Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve new user experience + adjust home load #5491

Merged
merged 13 commits into from
Jan 19, 2024

Conversation

RafaelsRamos
Copy link
Contributor

Feature - Improve UX for new users

Some users have the bad luck of not picking a working instance on the first try. This development aims to offer them support and guide them into selecting a working instance.

Types of instances

For new users, instances can be divided into three types.

  • Type A - An instance that we don't get a response from. Using such an instance results in having an infinite loading;
  • Type B - An instance that immediately results in the "Nothing here" screen;
  • Type C - A functional instance;

How can we improve the UX to facilitate the selection of an appropriate instance?

Depending on the type of instance the user has selected, the way the user will be guided will be different.
For users with an instance of:

  • Type A - After 10 seconds of trying to load content, the user will be prompted with a SnackBar, with an Action that takes him/her to the IntentSettings screen (video A) - This does not cancel the thread.;
  • Type B - On the "Nothing here" screen, two buttons were added:
    • "Retry" - The first time the user joins the App, he/she does not know that below exists a swipeable view;
    • "Change instance" - Button to facilitate the user journey into a functional instance;
  • Type C - Nothing! The App is good to go! 💪

Adjacent issues

While working on this feature, there were a couple of behaviours that could be improved:

  • When on an instance of Type A, swapping between tabs was causing multiple threads to be created and never cancelled - check this video for a showcase;
  • When loading the home screen and before finishing, swapping to another tab was causing the load not to be used (due to binding being null when it was time to fill the views) - sample below ("current_ui_behaviour.mp4");
  • Switching between tabs leads the home to reload every time - sample below ("before_tab_swap.mp4");
current_ui_behaviour.mp4
before_tab_swap.mp4

What does this PR do?

  • Create HomeViewModel responsible for the calls that fill the screen and is responsible for holding the LiveData instances that have the data being shown - Subscribing (on onResume) and Unsubscribing (on onPause) to these LiveData instances leads to a healthy UI/IO management, and supports data being loaded, even if we are not in the home screen;
  • Create two mechanisms that facilitate changing the instance;
  • closes Facilitate changing instance #5479;

Notes

  • Clarify the rationale behind the chosen approach.

Showcase

showcase.mp4

Showcase of the current approach cancelling previous, ongoing threads when creating new ones - video sample.

after_ui_behaviour.mp4
after_tab_swap.mp4

Test cases

This was the main test case I used to test this development. Please let me know if there are other "must test" flows.

  1. Fully wipe data;
  2. Select instance that fails immediately;
  3. Press Retry button a couple of times;
    3a. Ensure the progress bar briefly shows up, indicating ongoing attempt;
  4. Press change instance button;
    4a. Ensure redirection to Settings > Instance;
  5. Select a new instance to an instance that loads indefinetely;
  6. Go back all the way to the Home;
    6a. Ensure the progress bar is enabled, indicating an ongoing attempt;
  7. Wait a couple of seconds and switch to another tab;
  8. Wait around 20 seconds;
    8a. Ensure the SnackBar DOES NOT shows up;
  9. Go back to Home tab, and wait around 20 seconds;
  10. Wait 10 seconds;
    10a. Ensure the SnackBar DOES show up;
  11. Press the "Change" on the SnacBar;
  12. Change to a working instance;
  13. Go back to Home;
    13a. Ensure the Home screen is correctly loaded;

Closing Notes

I sincerely appreciate the review; I hope this improves both new users' UX and the home load.
Please let me know if any of the points or approaches mentioned do not make sense or if we should opt for another approach.

@RafaelsRamos
Copy link
Contributor Author

If you agree with the data binding approach, I would like to mention that for the future it would be very beneficial to have a repository layer.

At the moment view models make their calls directly on the Retrofit instances and the state is being saved either on LiveData at the view model level - e.g. SubscriptionsViewModel (this is already a great step, however does not support exchange of information between screens), or directly in fragments (usually the data is re-fetched when we re-enter the screen).

Here's a very simplified example of what it would look like:

class HomeFragment: Fragment() {
    private val viewModel: HomeViewModel by activityViewModels()

    override fun onResume() {
        super.onResume()
        viewModel.feedContent.observe(viewLifecycleOwner, ::showFeed)
    }

    override fun onPause() {
        super.onPause()
        viewModel.feedContent.removeObserver(::showFeed)
    }
    ...
}

class HomeViewModel(
    private val contentRepository: IContentRepository
): ViewModel() {
    val feedContent get() = contentRepository.feedContent

    init {
        viewModelScope.launch {
            contentRepository.loadFeed()
        }
    }
}

class ContentRepository: IContentRepository {
    override val feedContent: LiveData<List<StreamItem>?> get() = _feedContent
    private val _feedContent: MutableLiveData<List<StreamItem>> = MutableListData(null)

    override fun loadFeed() {
        if (feedContent.value == null) {
            // Load from API and fill live data
        }
    }
}

With this approach, all data fetched in one screen would be available in other screens that need the same information. All this management would be done inside the repositories

@RafaelsRamos
Copy link
Contributor Author

Thanks a lot for the review Bnyro 💪 I hope I have not forgotten any topic.

@Bnyro
Copy link
Member

Bnyro commented Jan 19, 2024

If you agree with the data binding approach, I would like to mention that for the future it would be very beneficial to have a repository layer.
At the moment view models make their calls directly on the Retrofit instances and the state is being saved either on LiveData at the view model level - e.g. SubscriptionsViewModel (this is already a great step, however does not support exchange of information between screens), or directly in fragments (usually the data is re-fetched when we re-enter the screen).

Personally, I don't think we need that additional level of abstraction.

I agree that refactoring parts of the app to make more use of view models to persist data is the right direction, but these additional layers would just cause a lot of additional code to maintain. It might be a common practice in the industry (I can't tell, I don't work there), but I think for us it's much better to keep things as short and simple as possible to keep maintenance as easy as possible.

We can use the same view model at different fragments to share data, which already works very well.

Hence we should only use such repository approaches if really needed, and I don't currently see a place in the app where it'd be really useful.

* Change buttons style for consistency;
* Move updateIfChanged to a separate file;
@RafaelsRamos
Copy link
Contributor Author

Great 🙌 I hope I addressed all points!

Regarding the repositories, I agree that at the moment it might not make sense since it is a shift from what we currently use. Thus, it would also lead to some adjustment time 👍

That being said, I would just like to point out a benefit of using repositories I forgot the first time.

On flows such as these:

  1. User is on HomePage;
  2. User presses on a Video and subscribes;
  3. Goes to Miniplayer;
  4. Home page is not refreshed with this subscription;

having a central point where information is fetched and observed from, ensures the UI is currently reflecting the most recent updates. Additionally, if the data (subscriptions in this case) is updated in a centralized place, there is no need to re-fetch information again when entering or re-entering screens that need it - resulting also in less busy instances (not sure about this part, please current me if I'm wrong).

That being said, I agree that it may not make sense such changes 👍

@Bnyro
Copy link
Member

Bnyro commented Jan 19, 2024

resulting also in less busy instances (not sure about this part, please current me if I'm wrong

Piped uses ActiveJ (web framework) supporting caching, and hence it detects that the request has already been made and thus cached before -> it will immediately return the cached response without that the server is doing any work to refresh the data.

After some time, the cached response will be removed from the cache, however when it is it's been enough time passed so that it really makes sense to refresh the data due to possible updates.

So the impact mostly should be minimal (as far as I know). But it might however improve the UX to not needing to wait for the cached response to be returned again.

Copy link
Member

@Bnyro Bnyro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@Bnyro Bnyro merged commit e31943f into libre-tube:master Jan 19, 2024
3 of 4 checks passed
@RafaelsRamos RafaelsRamos deleted the feat/5479 branch January 22, 2024 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Facilitate changing instance
2 participants