web-dev-qa-db-fra.com

Conserver les états de recyclage dans un fragment avec la bibliothèque de pagination et le composant d'architecture de navigation

J'utilise 2 composants du jetpack: la bibliothèque de pagination et la navigation.

Dans mon cas, j'ai 2 fragments: ListMoviesFragment & MovieDetailFragment

lorsque je fais défiler une certaine distance et clique sur un élément de film de la vue de recyclage, MovieDetailFragment est attaché et ListMoviesFragment est dans le backstack. Ensuite, j'appuie sur le bouton de retour, le ListMoviesFragment est ramené du backstack.

Le point est en position de défilement et les éléments de ListMoviesFrament sont réinitialisés exactement comme la première fois qu'ils sont attachés à son activité. Alors, comment garder les états de recyclage pour éviter cela?

D'une autre manière, comment conserver des états de fragment entier comme cacher/montrer un fragment avec FragmentTransaction de manière traditionnelle mais de manière moderne (navigation)

Mes exemples de codes:

disposition des fragments:

<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"/>

Code de kotlin de fragment:

    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()
        }
    }
13
Slim_user71169

Chaque fois que le fragment revient, il obtient la nouvelle PagedList, cela provoque l'adaptateur à présenter de nouvelles données, vous verrez donc la vue du recycleur se déplacer vers le haut.

En déplaçant la création de PagedList vers viewModel et en vérifiant de renvoyer l'existant PagedList au lieu d'en créer un nouveau, le problème sera résolu. Bien sûr, cela dépend de l'activité de votre application, la création d'une nouvelle PagedList peut nécessiter, mais vous pouvez la contrôler complètement. (ex: quand tirer pour rafraîchir, quand l'utilisateur entre des données pour rechercher ...)

1
Lạng Hoàng

Éléments à garder à l'esprit lors de la résolution de ces problèmes:

  1. Afin de permettre au système d'exploitation de gérer automatiquement la restauration de l'état d'une vue, vous devez fournir un identifiant. Je pense que ce n'est pas le cas (car vous devez identifier le RecyclerView pour lier les données).

  2. Vous devez utiliser le FragmentManager correct. J'ai eu le même problème avec un fragment imbriqué et tout ce que j'avais à faire était d'utiliser ChildFragmentManager.

  3. SaveInstanceState () est déclenchée par l'activité. Tant que l'activité est vivante, elle ne sera pas appelée.

  4. Vous pouvez utiliser ViewModels pour conserver l'état et le restaurer dans onViewCreated ()

Enfin, le contrôleur de navigation crée une nouvelle instance d'un fragment chaque fois que nous naviguons vers une destination. Évidemment, cela ne fonctionne pas bien avec le maintien d'un état persistant. Jusqu'à ce qu'il y ait un correctif officiel, vous pouvez vérifier la solution de contournement suivante qui prend en charge l'attachement/détachement ainsi que différentes backstacks par onglet. Même si vous n'utilisez pas BottomNavigationView, vous pouvez l'utiliser pour implémenter un mécanisme d'attachement/détachement.

https://github.com/googlesamples/Android-architecture-components/blob/27c4045aa0e40d402bbbde16d7ae0c9822a34447/NavigationAdvancedSample/app/src/main/Java/com/example/Android/navigationtavanced

0
Vaios

Désolé d'être en retard. J'ai eu un problème similaire et j'ai trouvé une solution (peut-être pas la meilleure). Dans mon cas, j'ai adapté les changements d'orientation. Ce dont vous avez besoin est d'enregistrer et de récupérer l'état LayoutManager ET la dernière clé de l'adaptateur.

Dans l'activité/le fragment qui montre votre RecyclerView, déclarez 2 variables:

private Parcelable layoutState;
private int lastKey;

Dans onSaveInstanceState:

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    if(adapter != null) {
        lastKey = (Integer)adapter.getCurrentList().getLastKey();
        outState.putInt("lastKey",lastKey);
        outState.putParcelable("layoutState",dataBinding.customRecyclerView
        .getLayoutManager().onSaveInstanceState());
    }      
 }

Dans onCreate:

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //.... setContentView etc...

    if(savedInstanceState != null) {
        layoutState = savedInstanceState.getParcelable("layoutState");
        lastKey = savedInstanceState.getInt("lastKey",0);
    }

    dataBinding.customRecyclerView
   .addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView   recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if(lastKey != 0) {
                dataBinding.customRecyclerView.scrollToPosition(lastKey);
                Log.d(LOG_TAG, "list restored; last list position is: " + 
                ((Integer)adapter.getCurrentList().getLastKey()));
                lastKey = 0;
                if(layoutState != null) {
                    dataBinding.customRecyclerView.getLayoutManager()
                    .onRestoreInstanceState(layoutState);
                    layoutState = null;
                }
            }
        }


    });
}

Et c'est tout. Votre RecyclerView devrait maintenant se restaurer correctement.

0
Ignacio Garcia