using setupWithNavController:2.5.0-alpha01 caused to OnViewCreated called every time

Viewed 204

I use the navigation component and bottom navigation bar and for handling the tab bar navigation, call the below code

binding.bottomNavigationView.setupWithNavController(navController)

Before the last version, I had a big problem saving the states of each fragment while using setupWithNavController(), but thanks to the latest version of navigation API(>=:2.4.0), it supports the multiple back stack, and I can easily navigate between tabs with no concerns about loading all the data again.

But I figured out that every time I navigate each tab, the OnViewCreated function is called! And this caused some issues for me.

For example, OnViewCreated is a place to collect flows using the repeatOnLifeCycle, and this repetitive call of OnViewCreated caused many subscribers of flows!

even in the document said:

The best practice is to call this function when the lifecycle is initialized. For example, onCreate in an Activity, or onViewCreated in a Fragment. Otherwise, multiple repeating coroutines doing the same could be created and be executed at the same time.

It assumes OnViewCreated as a place that is called once!

The obvious question is, firstly, is that behavior an issue or expected? And if it is expected, where can we collect the flows? And please elaborate on why it does happen?

1 Answers

setupWithNavController() function handles the multiple backstack and that is really great:

bottomNavigationView.setupWithNavController(getNavController())

But I had some issues with that. To be on the same page, let's review the scenario one more time quickly:

just assume there are three tabs, each representing a separate nested graph.

As I mentioned in my question, using setupWithNavController sometimes causes to call onViewCreated function by navigating between tabs.

I discovered that even in some circumstances, the fragment would recreate each time. (when you click on another tab while the current tab is showing a fragment that is not the start the destination for the selected nested graph), the fragment is going to recreate each time.

Problems which I had:

- collecting viewModel's flows in the fragment (when onViewCreated is called but the fragment is not recreated):

To solve this issue, I decided to use viewLifecycleOwner.lifecycleScope instead of the lifecycleScope of the fragment. However, by navigating to another tab, the fragment still existed, but the view was destroyed so I could make sure that switching between tabs would not create the issue of having multiple observations of flows.

    viewModel.notifyList.flowWithLifecycle(
        viewLifecycleOwner.lifecycle,
        Lifecycle.State.RESUMED
    ).onEach {
        doSomething()
    }.launchIn(viewLifecycleOwner.lifecycleScope)

- storing the last state of lists (when the fragment is recreated)

So if the fragment is going to be recreated each time, how could I store the state of my recyclerview adapter, which is implemented with the pagination3 library?

So as you know, there is a paging source that returns a flow of PagingData. Let's say there is a function named getData()

 private fun getData(): Flow<PagingData<LeitnerWordModel>> {
        return Pager(
            config = PagingConfig(),
            initialKey = 0,
            pagingSourceFactory = {
                /**Pager expected to create a new instance. Ensure that the pagingSourceFactory passed to Pager always returns a new instance of PagingSource.*/
                MyPagingSource()
            }
        ).flow
    }

Previously I called getData() from the fragment, but by recreating the fragment, I used to lose the last state of the list as every time it had to create a new instance of the Pager.

So as all we know, the ViewModel has a longer lifecycle, so I just called getData() in the init block of viewModel only once.

And used a Stateflow (or SharedFlow with replay=1) to emit the last recent data to the fragment:

    class MyViewModel:ViewModel(){

           private val _notifyList =
        MutableSharedFlow<PagingData<MyModel>>(replay = 1, extraBufferCapacity = 0, onBufferOverflow = BufferOverflow.SUSPEND)

    val notifyList: SharedFlow<PagingData<MyModel>> get() = _notifyList

        init {
            getData().cachedIn(viewModelScope).onEach {

                     _notifyList.emit(it)

              }.launchIn(viewModelScope)

             }
    }
Related