Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The Fragment refreshes on back- Android Navigation

I am making an application using the Android Navigation component. But I ran into a very fundamental problem which can cause problems in the whole development of my application.

The Scenario

I have this Fragment where in onViewCreated I am observing a field from my viewmodel.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProviders.of(this).get(EventDetailsViewModel::class.java)
    viewModel.init(context!!,eventId)

    viewModel.onEventDetailsUpdated().observe(this, Observer {
        setEventDetails(it)
    })

}

And in the setEventDetails method, I set recyclerviews with the data.

The PROBLEM

This fragment is a long fragment with a scroll. Suppose I scroll long way down to a section and click on a button which takes me to another fragment.

But when I come back to this fragment, it again takes me to the top and does everything that it did on first load. That can be troubling. It is kind of recreating the whole fragment instead of keeping its old state.

What I tried

I searched a lot of questions. And went through This Github Query, This SO question, Another Git... But I could not solve my problem.

Please help, Thanks in advance.

like image 520
The Bat Avatar asked Jul 26 '19 06:07

The Bat


2 Answers

Yes, Fragment's view will get destroyed whenever you navigate forward to another fragment.

RecyclerView's scroll position should be automatically restored, even when new instance of RecyclerView is created and new Adapter instance is set, as long as you setup everything with the same dataset as before. Also, you need to do it before the first layout pass.

This means that you need your old data and you need to have it ready immediately (no async loads!). ViewModelProvider should return the same ViewModel instance. That ViewModel holds the data you should be able to synchronously get and display on the UI. Make sure to refactor your viewModel.init method - you don't want to make API call if data is already there in case when going back. A simple boolean isInitialized can work here, or you can even check if LiveData is empty or not.

Also, you have a subtle bug when calling observe on LiveData. onViewCreated can be called many times for the same fragment (each time you navigate forward and back!) - so observe will be called each time. Your Fragment will be subscribed many times to the same LiveData. This means you will get events multiple times (once for each subscription). This can cause issues with RecyclerView state restoration too. Your subscription is tied to Lifecycle owner you passed. You passed Fragment's Lifecycle owner which is tied to Fragment's lifecycle. What you want to do is pass Fragment view's lifecycle owner, so whenever the view is destroyed the subscription gets cleared, and you only have 1 subscription ever and only while the Fragment's view is alive. For this, you can use getViewLifecycleOwner instead of this.

like image 114
Singed Avatar answered Nov 05 '22 01:11

Singed


You need to rely on ViewModel to restore the fragment state because ViewModel doesn't get destroyed on fragment change. In your viewModel, create a variable listState

class HomeViewModel : ViewModel() {
    var listState: Parcelable? = null
}

Then in your fragment use below code


class HomeFragment : Fragment() {
    private val viewModel by navGraphViewModels<HomeViewModel>(R.id.mobile_navigation)

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        if (viewModel.listState != null) {
            list.layoutManager?.onRestoreInstanceState(viewModel.listState)
            viewModel.listState = null
        }else{
           //load data normally
    }

    override fun onDestroyView() {
        super.onDestroyView()
        viewModel.listState = list.layoutManager?.onSaveInstanceState()
    }
}
like image 22
Akash Chaudhary Avatar answered Nov 05 '22 03:11

Akash Chaudhary