web-dev-qa-db-fra.com

IllegalStateException: <MyFragment> n'est pas actuellement dans FragmentManager.

Je sais que cela ressemble à une copie de FragmentStatePagerAdapter IllegalStateException: <MyFragment> n'est pas actuellement dans FragmentManager

Je reçois très rarement l'accident suivant:

Java.lang.RuntimeException: Impossible de suspendre l'activité {MyActivity}: 

...

Causée par: Java.lang.IllegalStateException: Fragment MyFragment {40648258 id = 0x7f070051} n'est pas actuellement dans le FragmentManager en Android.support.v4.app.FragmentManagerImpl.putFragment (MT: 516) à l'adresse Android.support.v4.app.FragmentStatePagerAdapter.saveState (MT: 185) à l'adresse Android.support.v4.view.ViewPager.onSaveInstanceState (MT: 881) 

...

sur Android.view.View.saveHierarchyState (View.Java:6238) sur com.Android.internal.policy.impl.PhoneWindow.saveHierarchyState (PhoneWindow.Java:1522) sur Android.app.Activity.onSaveInstanceState (Activity.Java:1138) sur Android.support.v4.app.FragmentActivity.onSaveInstanceState (MT: 480) à l'adresse MyActivity.onSaveInstanceState (MT: 336)

Il semble que ce soit le code étrange que je ne peux pas comprendre de FragmentStatePagerAdapter:

for (int i=0; i<mFragments.size(); i++) {
    Fragment f = mFragments.get(i);
    if (f != null) {
        if (state == null) {
            state = new Bundle();
        }
        String key = "f" + i;
        mFragmentManager.putFragment(state, key, f);
    }
}

Il semble que l'adaptateur tire ma Fragment de mFragments mais ne puisse pas ajouter son état à FragmentManager.

Je n'ai trouvé aucun moyen de recréer cela sur mes appareils de test, je ne l'ai reçu que de certains utilisateurs.

J'utilise le package de support v4.

Toute aide? Merci.

34
marmor

La FragmentStatePagerAdapter est un morceau de code horrible, criblé de bogues, reconnu ou non par Google. J'utilise donc ce code pour résoudre ce problème particulier:

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // Yet another bug in FragmentStatePagerAdapter that destroyItem is called on fragment that hasnt been added. Need to catch
        try {
            super.destroyItem(container, position, object);
        } catch (IllegalStateException ex) {
            ex.printStackTrace();
        }
    }
16
Greg Ennis

Cela peut arriver lorsqu'un FragmentStatePagerAdapter est défini sur un ViewPager, les fragments étant en cours de remplissage, mais non encore ajoutés à FragmentManager en raison de la pause d'activité avant la mise en page de ViewPager. Pour ce faire, démarrez une autre activité de onCreate de manière fiable, l'adaptateur étant également défini sur onCreate. Dans ce cas, il est préférable de ne pas configurer l'adaptateur et de l'activer à la place. Voir ce numéro: https://code.google.com/p/Android/issues/detail?id=77285

J'ai également soumis un correctif pour vérifier si le fragment avait été ajouté avant d'essayer de sauvegarder l'état.

10
piusvelte

J'ai rencontré l'exception exacte.

Dans mon cas, j'ai plusieurs fragments gérés par FragmentPagerStateAdapter, mais parfois, le fragment sera inversé. 

Je remplace getItemPosition (), renvoie la nouvelle position et appelle notifyDataSetChanged () pour actualiser ViewPager. Tout fonctionne bien, mais parfois, lorsque je quitte ViewPager, le blocage se produit.

Après plusieurs heures de travail, j'ai trouvé un bogue dans FragmentPagerStateAdapter.

L’adaptateur met non seulement les états en cache, mais également les fragments qu’il considère comme «actifs»

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

Mais il ne vérifie pas si la position des fragments a été invalidée ou déplacée.

Voilà comment il obtient cette exception:

1.J’ai A, B, C trois fragments dans ViewPager et Adaptateur.

2.J'ai commuté la position sur B, A, C dans l'adaptateur et appelé notifyDataSetChanged ().

3.ViewPager réorganise le fragment par nouvel ordre. Mais le cache mFragments est toujours {A, B, C}

4.Faites glisser quelques pages, ViewPager demandera à l’adaptateur de détruire le premier fragment. alors le fragment B, au lieu de A, est défini sur null dans le cache. Maintenant le mFragments est {null, A, C}.

5.Laissez ViewPager et onSaveInstanceState est déclenché. tous les fragments dans mFragments sont considérés comme actifs et putFragment () est appelé jusqu'à ce que FragmentManagerImpl trouve A pas dedans et lève une exception.

Alors voici ma solution pas si douce:

1.Copiez l'intégralité du code source de FragmentPagerStateAdapter.

2. Remplacez la méthode notifyDataSetChanged () pour réorganiser les caches comme indiqué ci-dessous.

@Override
public void notifyDataSetChanged() {
  List<Fragment> oldFragments = new ArrayList<>(mFragments);
  List<Fragment.SavedState> oldStates = new ArrayList<>(mSavedState);
  for (int i = 0; i < getCount(); i++) {
    if (i < mFragments.size()) {
      Fragment f = mFragments.get(i);
      if (f != null) {
        int newPosition = getItemPosition(f);
        if (newPosition == POSITION_UNCHANGED || newPosition == i) {
        } else if (newPosition == POSITION_NONE) {
          if (i < mSavedState.size()) {
            mSavedState.set(i, null);
          }
          mFragments.set(i, null);
        } else {
          while (i >= mFragments.size()) {
            mFragments.add(null);
          }
          if (oldStates.size() > i) {
            mSavedState.set(newPosition, oldStates.get(i));
          }
          mFragments.set(newPosition, oldFragments.get(i));
        }
      } else {
        /*
         * No Fragment but that's possible there's savedState and position has
         * changed.
         */
      }
    }
  }
  super.notifyDataSetChanged();
}

J'espère que cela fonctionne pour certains d'entre vous!

7
Joseph

Utilisez getChildFragmentManager() au lieu de getFragmentManager() si vous avez une hiérarchie de fragments. par exemple. si vous avez des fragments pager.

7
ViliusK

Utilisez le suivant 

final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(f, key);

au lieu de 

mFragmentManager.putFragment(state, key, f);

et passer le paquet explicitement ..

Pour référence,

http://developer.Android.com/reference/Android/app/FragmentTransaction.html#add%28Android.app.Fragment,%20Java.lang.String%29

2
Sripathi

Écrire dans la méthode d'activité onCreate ():

pager = (ViewPager) findViewById(R.id.pager);
    adapter = new SwipePagerAdapter(getSupportFragmentManager());
    pageOneFragment = new PageOneFragment();
    adapter.addFragment(pageOneFragment);

Code de l'adaptateur:

public class SwipePagerAdapter extends FragmentStatePagerAdapter
{
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public SwipePagerAdapter(FragmentManager fm)
{
    super(fm);
}

@Override
public Fragment getItem(int position)
{
    return mFragments.get(position);
}

@Override
public int getCount()
{
    return mFragments.size();
}

public void addFragment(Fragment fragment)
{
    mFragments.add(fragment);
    notifyDataSetChanged();
}

@Override
public void destroyItem(ViewGroup container, int position, Object object)
{
    super.destroyItem(container, position, object);
}

@Override
public CharSequence getPageTitle(int position)
{
    return super.getPageTitle(position);
}}
1
Abdul Rehman

Appelez-vous FragmentStatePagerAdapter.instantiateItem () à partir de votre propre code? En règle générale, ViewPager doit être le seul client à appeler cette méthode. Toutefois, il est facile de trouver des solutions de contournement pour les autres limitations qui reposent sur cette méthode.

En raison de la façon dont fonctionne FragmentStatePagerAdapter, je suppose que l'adaptateur a instancié le fragment, l'a ajouté à sa collection interne de fragments et a même lancé une transaction pour ajouter ce fragment au gestionnaire de fragments, mais cette transaction n'a pas été validée ni exécutée. . C’est exactement ce que fait instantiateItem() quand il n’a pas déjà instancié le fragment demandé.

Une autre méthode, FragmentStatePagerAdapter.finishUpdate(), est responsable de la validation de la transaction ainsi que de l’exécution des transactions en attente. finishUpdate () doit être appelé après instantiateItem () sinon le fragment ne peut pas être ajouté à FragmentManager.

Essayez quelque chose comme ça:

// Get the current Fragment presented by the ViewPager.
pagerAdapter.startUpdate(viewPager);
Fragment fragment = pagerAdapter.instantiateItem(viewPager, viewPager.getCurrentItem());
pagerAdapter.finishUpdate(viewPager);
return fragment;

startUpdate () a une implémentation vide à partir du niveau 19 de l'API.

1
James Wald

Vous pouvez essayer d'utiliser mFragmentManager.add();

0
superuser

Essayez d'utiliser 

use fragmentTransaction.add()
0
Bedimindtricks

Vérifiez si vous avez implémenté onSaveInstanceState et onRestoreInstanceState dans votre activité et vérifiez qu'ils enregistrent et chargent correctement l'état de vos fragments.

0
Hubert

Les fragments dans ViewPager sont corrigés. Au lieu d'essayer de remplacer les fragments de l'adaptateur, essayez de donner un ensemble de fragments différent et notifyDataSet modifié, ou profitez de FrameLayout pour afficher un autre fragment par-dessus le fragment actuel de l'onglet Vue.

Il y a ma solution qui fonctionne:

Le geste de glissement appliqué au niveau de fragment avec ViewPager avec son glissement par défaut désactivé

0
Abhinav Saxena