Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()
        }
    }
like image 855
Slim_user71169 Avatar asked Jan 10 '19 09:01

Slim_user71169


3 Answers

Since you use NavController, you cannot keep the view of the list fragment when navigating.

What you could do instead is to keep the data of the RecyclerView, and use that data when the view is recreated after back navigation.

The problem is that your adapter and the singersPagination is created anew every time the view of the fragment is created. Instead,

  1. Move singersAdapter to a field:

    private val singersAdapter = HomeSingersAdapter()
    
  2. Move this part to onAttach

    getSingersPagination().observe(viewLifecycleOwner, Observer {
        singersAdapter.submitList(it)
    })
    
  3. Call retainInstance(true) in onAttach. This way even configuration changes won't reset the state.

like image 121
Janos Breuer Avatar answered Oct 19 '22 06:10

Janos Breuer


On fragment's onSaveinstanceState save the layout info of the recyclerview:

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable(KEY_LAYOUT, myRecyclerView.getLayoutManager().onSaveInstanceState());
    }

and on onActivityCreated, restore the scroll position:

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null) {
           myRecyclerView.getLayoutManager().onRestoreInstanceState(
                    savedInstanceState.getParcelable(KEY_LAYOUT));
        }
    }
like image 44
vokod Avatar answered Oct 19 '22 06:10

vokod


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.
like image 2
artenson.art98 Avatar answered Oct 19 '22 07:10

artenson.art98