10
votes

On s'attendait à ce que l'adaptateur soit `` frais '' lors de la restauration de l'état

J'ai un viewpager2 avec plusieurs fragments dans FragmentStateAdapter. Chaque fois que j'essaye d'ouvrir un nouveau fragment, puis de revenir à mon fragment actuel avec viewpager2, j'obtiens une exception:

Expected the adapter to be 'fresh' while restoring state.

Il semble que FragmentStateAdapter est incapable de restaurer correctement son état car il s'attend à ce qu'il soit vide.

Que puis-je faire pour résoudre ce problème?


2 commentaires

Jetez un œil à ce speakman.net.nz/blog/2014/02/20/…


@ nimi0112 Vous avez recherché l'erreur sur Google et choisi le premier résultat de Google. Le fait est que le lien n'aide pas


7 Réponses :


5
votes

Mon problème était donc que je créais mon FragmentStateAdapter dans mon champ de classe Fragment où il n'a été créé qu'une seule fois. Ainsi, lorsque mon onCreateView a été appelé une deuxième fois, j'ai eu ce problème. Si je recrée l'adaptateur à chaque appel onCreateView, cela semble fonctionner.


6 commentaires

C'est une très mauvaise solution. Dans le résultat, vous allez créer plusieurs instances d'adapeter, ce qui ralentit votre application


@GeorgiyChebotarev J'ai une terrible expérience de retard probablement à cause de cela. Mon BottomNavigationView est à l'intérieur d'un fragment et un si son onglet contient un ViewPager2 avec 3 pages et lorsque je passe d'un autre onglet de BottomNavigationView à un avec ViewPager2, j'ai un mauvais décalage. ViewPager2 fonctionne également plus lentement que ViewPager


@Thracian J'ai rétabli mon code à la vieille école viewPager. Pour ce cas, l'adaptateur pourrait être réutilisé et je le crée dans la méthode onCreate. Ça a l'air pas mal. Si cela ne vous aide pas, essayez d'utiliser une disposition asynchrone gonflante pour les fragments internes.


@GeorgiyChebotarev, merci. Je vais vérifier cela. Pour l'instant, je vais l'essayer avec ViewPager2 et la mise en page asynchrone et voir comment cela fonctionnera. Avez-vous ViewPager une différence notable de performances après le retour à ViewPager ? Le temps qu'il faut entre le début de onCreteView (avant de gonfler quoi que ce soit) et onResume après le passage au fragment avec ViewPager2 est d'environ 80 ms, ce qui signifie qu'il manque 5 images.


@Thracian, je voulais utiliser viewPager2, mais il a des bugs importants pour l'architecture à activité unique. Je ne peux pas l'utiliser, donc je suis revenu à viewPgaer. Non, je ne vois pas la grande différence entre le premier et le deuxième visualiseur.


@Georgiy Chebotarev Le pire des cas est que le document officiel recommande de l'utiliser à la place d'un fragmentPagerAdapter. Le principal problème est que le constructeur a besoin d'un viewLifeCycle qui ne peut être obtenu qu'après viewCreated, alors que les fragments qu'il stocke survivront sans aucun doute à ce cycle. Si vous essayez même de contourner la recréation inutile de l'adaptateur / des fragments, un compte à rebours de 10 secondes est mis en place pour détruire tout fragment non visible lors de la réentrée de backStack. C'est un cauchemar.



4
votes

J'ai rencontré le même problème avec ViewPager2. Après de nombreux efforts pour tester différentes méthodes, cela a fonctionné pour moi:

public void onResumeOfYourFragment() {
    viewPager2.setAdapter(yourAdapter);
}

Lorsque vous revenez au fragment à nouveau:

public void onExitOfYourFragment() {
    viewPager2.setAdapter(null);
}


3 commentaires

On dirait que c'est une solution similaire, mais faite d'une manière plus appropriée, ce qui nous permet de réutiliser l'adaptateur. Bonne trouvaille! :)


Je ne peux pas faire fonctionner ça. Si je le définis sur null dans onDestroyView, la propriété mFragments n'est toujours pas vide lors de la restauration de l'état des adaptateurs, ce qui génère cette erreur.


Cela n'a pas fonctionné pour moi, et il semblerait que l'erreur produite par ViewPager2 indique qu'il ne veut pas réutiliser l'adaptateur - donc la réponse de @SMGhost semble plus appropriée.



1
votes

java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state. également cette java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state. je java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state. lors de l'utilisation de ViewPager2 dans un fragment.

Il semble que le problème soit dû au fait que mViewPager2.setAdapter(mFragmentStateAdapter); dans ma méthode onCreateView () .

Je l'ai corrigé en déplaçant mViewPager2.setAdapter(mMyFragmentStateAdapter); à ma méthode onResume () .


0 commentaires

0
votes

J'ai résolu ce problème en testant s'il est égal à null

if(recyclerView.adapter == null) {recyclerView.adapter = myAdapter}


0 commentaires

0
votes

J'ai eu du mal avec cela et aucune des réponses précédentes n'a aidé.

Cela peut ne pas fonctionner pour toutes les situations possibles, mais dans mon cas, les fragments contenant ViewPager2 ont été corrigés et peu nombreux, et j'ai résolu ce problème en effectuant un changement de fragment avec les méthodes show() et hide() FragmentTransaction , au lieu de replace() couramment recommandé pour ce. Appliquez show() au fragment actif et hide() à tous les autres. Cela évite les opérations telles que la recréation de vues et la restauration de l'état qui déclenchent le problème.


0 commentaires

1
votes

il peut être corrigé par viewPager2.isSaveEnabled = false


1 commentaires

Cela a fonctionné pour moi! J'ai un ViewPager2 qui page des fragments chacun avec leur propre RecycleView, plus un TabLayout en haut. Le ViewPager (anciennes et nouvelles versions) ne recréait pas les fragments après la rotation. Cela a résolu le problème.



0
votes

Cet adaptateur / vue est utile en remplacement de FragmentStatePagerAdapter.

Si ce que vous cherchez est de préserver les fragments lors de la rentrée de Backstack, cela serait extrêmement difficile à réaliser avec cet adaptateur.

L'équipe a mis de nombreuses pauses en place pour éviter cela, Dieu seul sait pourquoi ...

Ils auraient pu utiliser un observateur de cycle de vie auto-détachant, dont la capacité était déjà prévue dans son code, mais nulle part dans l'architecture Android n'utilise cette capacité ...

Ils auraient dû utiliser ce composant inachevé pour écouter le cycle de vie global des Fragments au lieu de son viewLifeCycle, à partir de là, on peut mettre à l'échelle l'écoute du Fragment au viewLifeCycle. (attacher / détacher l'observateur viewLifeCycle ON_START / ON_STOP)

Deuxièmement ... même si cela est fait, le fait que le viewPager lui-même soit construit sur un recyclerView rend extrêmement difficile la gestion de ce que vous attendez du comportement d'un Fragment, qui est un état de conservation, une instanciation unique, et un cycle de vie bien défini (destruction contrôlable / attendue).

Cet adaptateur est contradictoire dans sa fonctionnalité, il vérifie si le viewPager a déjà été alimenté en Fragments, tout en nécessitant toujours un adaptateur «frais» lors de la réentrée.

Il préserve les fragments à la sortie du backStack, tout en s'attendant à les recréer tous lors de la réentrée.

Les pauses sur place pour empêcher un adaptateur instancié par champ, en supposant que toutes les autres variables sont déjà prises en compte pour une gestion correcte de viewLifeCycle (enregistrement / désinscription et définition et réinitialisation des paramètres) sont:

public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
        @NonNull Lifecycle lifecycle) {
    mFragmentManager = fragmentManager;
    mLifecycle = lifecycle;
    super.setHasStableIds(true);
}

Deuxième pause:

private void scheduleGracePeriodEnd() {
    final Handler handler = new Handler(Looper.getMainLooper());
    final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            mIsInGracePeriod = false;
            gcFragments(); // good opportunity to GC
        }
    };

    mLifecycle.addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                handler.removeCallbacks(runnable);
                source.getLifecycle().removeObserver(this);
            }
        }
    });

    handler.postDelayed(runnable, GRACE_WINDOW_TIME_MS);
}

où mFragmentMaxLifecycleEnforcer doit être == null lors de la réentrance ou il lève une exception dans checkArgument ().

Troisièmement: un ramasse-miettes de fragments mis en place lors de la réentrée ( vers la vue, à partir de la pile arrière) qui est post-retardé à 10 secondes qui tente de détruire les fragments hors écran, provoquant des fuites de mémoire sur toutes les pages hors écran car il tue leurs FragmentManagers respectifs qui contrôlent leur Cycle de la vie.

@CallSuper
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    checkArgument(mFragmentMaxLifecycleEnforcer == null);
    mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
    mFragmentMaxLifecycleEnforcer.register(recyclerView);
}

Et tous à cause de son principal coupable: le constructeur:

@Override
    public final void restoreState(@NonNull Parcelable savedState) {
        if (!mSavedStates.isEmpty() || !mFragments.isEmpty()) {
            throw new IllegalStateException(
                    "Expected the adapter to be 'fresh' while restoring state.");
        }
.....
}


0 commentaires