androidandroid-recyclerviewandroid-pagingandroid-architecture-navigation

Keeping states of recyclerview in fragment with paging library and navigation architecture component


I'm using 2 components of the jetpack: Paging library and Navigation.

In my case, I have 2 fragment: ListMoviesFragment & MovieDetailFragment

when I scroll a certain distance and click a movie item of the recyclerview, MovieDetailFragment is attached and the ListMoviesFragment is in the backstack. Then I press back button, the ListMoviesFragment is bring back from the backstack.

The point is scrolled position and items of the ListMoviesFrament are reset exactly like first time attach to its activity. so, how to keep states of recyclerview to prevent that?

In another way, how to keep states of whole fragment like hide/show a fragment with FragmentTransaction in traditional way but for modern way(navigation)

My sample codes:

fragment layout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         tools:context="net.karaokestar.app.SplashFragment">

<TextView
        android:id="@+id/singer_label"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="Ca sĩ"
        android:textColor="@android:color/white"
        android:layout_alignParentLeft="true"
        android:layout_toLeftOf="@+id/btn_game_more"
        android:layout_centerVertical="true"
        android:background="@drawable/shape_label"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="@dimen/header_margin_bottom_list"
        android:textStyle="bold"
        android:padding="@dimen/header_padding_size"
        android:textAllCaps="true"/>


<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_singers"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

Fragment kotlin code:

    package net.karaokestar.app
    import android.os.Bundle
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.fragment.app.Fragment
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.Observer
    import androidx.navigation.fragment.findNavController
    import androidx.paging.LivePagedListBuilder
    import androidx.paging.PagedList
    import androidx.recyclerview.widget.LinearLayoutManager
    import kotlinx.android.synthetic.main.fragment_splash.*
    import net.karaokestar.app.home.HomeSingersAdapter
    import net.karaokestar.app.home.HomeSingersRepository

    class SplashFragment : Fragment() {

        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_splash, container, false)
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val singersAdapter = HomeSingersAdapter()
            singersAdapter.setOnItemClickListener{
findNavController().navigate(SplashFragmentDirections.actionSplashFragmentToSingerFragment2())
}
            list_singers.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            list_singers.setHasFixedSize(true)
            list_singers.adapter = singersAdapter

            getSingersPagination().observe(viewLifecycleOwner, Observer {
                singersAdapter.submitList(it)
            })
        }

        fun getSingersPagination() : LiveData<PagedList<Singer>> {
            val repository = HomeSingersRepository()
            val pagedListConfig = PagedList.Config.Builder().setEnablePlaceholders(true)
                .setPageSize(Configurations.SINGERS_PAGE_SIZE).setPrefetchDistance(Configurations.SINGERS_PAGE_SIZE).build()

            return LivePagedListBuilder(repository, pagedListConfig).build()
        }
    }

Solution

  • Try the following steps:

    1. Initialize your adapter in onCreate instead of onCreateView. Keep the initialization one time, and attach it in onCreateView or onViewCreated.
    2. Don't return a new instance of your pagedList from getSingersPagination() method everytime, instead store it in a companion object or ViewModel (preferred) and reuse it.

    Check the following code to get a rough idea of what to do:

    class SingersViewModel: ViewModel() {
       private var paginatedLiveData: MutableLiveData<YourType>? = null;
    
       fun getSingersPagination(): LiveData<YourType> {
          if(paginatedLiveData != null) 
             return paginatedLiveData;
          else {
             //create a new instance, store it in paginatedLiveData, then return
          }
       }
    }
    

    The causes of your problem are:

    1. You are attaching a new adapter each time, so it jumps to top.
    2. You are creating new paged list each time, so it jumps to the top, thinking the data is all new.