4

I am using Navigation component in my App, using google Advanced Sample(here). my problem is when going back to a fragment, the scrolling position does not lost but it rearranges items and moves highest visible items so that top of those item align to top of recyclerview. please see this:

enter image description here

before going to next fragment:

enter image description here

and after back to fragment:

enter image description here

this problem is matter because some times clicked item goes down and not seen until scroll down. how to prevent this behavior?

please consider:

  • this problem exist if using navigation component to change fragment. if start fragment using supportFragmentManager.beginTransaction() or start another activity and then go to this fragment it is OK. but if I navigate to another fragment using navigation component this problem is exist.(maybe because of recreating fragment)

  • also this problem exist if using fragment in ViewPager. i.e recyclerView is in a fragment that handle with ViewPagerAdapter and viewPager is in HomeFragment that opened with Navigation component. if recyclerView is in HomeFragment there is no problem.

  • no problem with LinearLayoutManager. only with StaggeredGridLayoutManager.

  • there is not difference if using ViewPager2 and also FragmentStatePagerAdapter

  • I try to prevent recreate of fragment(by this solution) but not solved.

UPDATE: you can clone project with this problem from here

Hadi Ahmadi
  • 1,924
  • 2
  • 17
  • 38
  • I can't spend time on project setup, but if you can provide a functional demo which I can fork and run, then I can help. – Haris Jan 05 '21 at 10:29
  • 1
    @HarisDautović thanks for your respone. you can get that from this link: https://github.com/thawri1/MyStaggeredListSample – Hadi Ahmadi Jan 05 '21 at 12:55

2 Answers2

2

When using Navigation Component + ViewPager + StaggeredGridLayoutManager, wrong recyclerView.computeVerticalScrollOffset() has been returned during Fragment recreate.

In general, all layout managers bundled in the support library already know how to save and restore scroll position, but in this case, we had to take responsibility for this.

class TestFragment : Fragment(R.layout.fragment_test) {

    private val testListAdapter: TestListAdapter by lazy {
        TestListAdapter()
    }

    private var layoutManagerState: Parcelable? = null

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

        postListView.apply {
            layoutManager = StaggeredGridLayoutManager(
                2, StaggeredGridLayoutManager.VERTICAL
            ).apply {
                gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
            }
            setHasFixedSize(true)

            adapter = testListAdapter
        }

        testListAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT

    }

    override fun onPause() {
        saveLayoutManagerState()
        super.onPause()
    }

    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        restoreLayoutManagerState()
    }

    private fun restoreLayoutManagerState () {
        layoutManagerState?.let { postListView.layoutManager?.onRestoreInstanceState(it) }
    }

    private fun saveLayoutManagerState () {
        layoutManagerState = postListView.layoutManager?.onSaveInstanceState()
    }
}

Source code: https://github.com/dautovicharis/MyStaggeredListSample/tree/q_65539771

Hadi Ahmadi
  • 1,924
  • 2
  • 17
  • 38
Haris
  • 4,130
  • 3
  • 30
  • 47
  • 1
    thank you. its working in sample project. but not in my real project! In fact, I have already tried your solution in my real app but it did not solved the problem. After your answer, I tried again. But I did not see any change. I also copied almost whole sample files to real project but not see change too. after that, I copy all my real app dependencies to sample app. now problem return to sample project! after so many try and error I realize the problem is from Paging 3 library(just imported and not used)! – Hadi Ahmadi Jan 06 '21 at 16:19
  • 1
    the dependency is: implementation "androidx.paging:paging-runtime-ktx:3.0.0-alpha11" – Hadi Ahmadi Jan 06 '21 at 16:21
  • I am guessing exclude something from paging solves problem But I do not know what – Hadi Ahmadi Jan 06 '21 at 16:21
  • 1
    It doesn't happen on latest stable version -> implementation "androidx.paging:paging-runtime-ktx:2.1.2". – Haris Jan 06 '21 at 16:40
  • 1
    I know that. but version 3 and 2 is very different. and my app is almost ready to publish. maybe I must create new issue for paging 3. – Hadi Ahmadi Jan 06 '21 at 16:46
  • problem is from RecyclerView. paging library using alpha version of RecyclerView that have this problem. https://stackoverflow.com/a/65606959/10109704 – Hadi Ahmadi Jan 07 '21 at 05:15
  • my problem solved by using: testListAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT and custom restoration logic – Hadi Ahmadi Jan 09 '21 at 12:28
  • @HadiAhmadi Thank you. I can update the answer, but also, if you have permission to edit feel free to do it. – Haris Jan 09 '21 at 12:46
  • @HadiAhmadi Do we still need save and restore layoutManager state manually when using this new solution? – Haris Jan 09 '21 at 13:01
1

The Navigation Component behavior is normal when you navigate from one fragment to another. I mean, onDestroyView() method from the previous fragment is executed, so it means that your view is destroyed, but not the fragment. Remembering that fragment has two lifecycles one for the fragment and another one for the view, There was a video about it.

Also, there were issues registered in issue tracker in order to avoid this behavior in some cases and the GitHub issues:

The problem is that when you have fragment that is heavy to recreate, is easier to do not destroy it and just add one fragment. So, when you go back it is not recreated. But, for this behavior is not part of navigation component.

Solutions

  • The easiest solution is to not use navigation component and work with the tradicional way, as you can see this works perfectly in you use case.

  • You can use the traditional way just for this use case, and use the navigation component for other cases.

  • You can inflate this view in an activity. So you are adding un activity

  • But if the previous tree options is not possible. You can try the following:

    • If you are using viewModel, you can use SaveState. Basically, it can save the data from your fragment, it is like a map data structure, so you can save positions from your list or recycler view. When go back to this fragment, get the position from this saveState object and use scrollToPosition method in order to add the real position.
    • Recycler view have methods for restore positions. You can see the uses cases for that, because first you need the data and then add the real position, for more details you can visit this link. This configuration for recycler view is useful also when you lose memory and you need to recreate the recycler view with asynchronous data.

Finally, if you want to understand more about how fragment works with navigation component, you can see this link

rguzman
  • 319
  • 3
  • 8