IllegalStateException: impossible d'exécuter cette action après onSaveInstanceState avec ViewPager

496

Je reçois des rapports d'utilisateurs de mon application sur le marché, générant l'exception suivante:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)

Apparemment, cela a quelque chose à voir avec un FragmentManager, que je n'utilise pas. Le stacktrace ne montre aucune de mes propres classes, donc je n'ai aucune idée où cette exception se produit et comment l'empêcher.

Pour mémoire: j'ai un tabhost, et dans chaque onglet il y a un ActivityGroup qui bascule entre les activités.

nhaarman
la source
2
J'ai trouvé cette question discutant du même problème, mais aucune solution là non plus .. stackoverflow.com/questions/7469082/…
nhaarman
3
Pendant que vous n'utilisez pas FragmentManager, Honeycomb l'est certainement. Cela se produit-il sur de vraies tablettes Honeycomb? Ou est-ce que quelqu'un exécute un Honeycomb piraté sur un téléphone ou quelque chose et c'est cette édition piratée qui a du mal?
CommonsWare
1
Je n'ai aucune idée. C'est la seule information que j'obtiens dans la Market Developer Console, le message utilisateur ne contient aucune information utile non plus ..
nhaarman
J'utilise Flurry, qui me montre 11 sessions avec Android 3.0.1, et j'ai 11 rapports de cette exception. Cela pourrait être une coïncidence cependant. Android 3.1 et 3.2 ont respectivement 56 et 38 sessions.
nhaarman
Le rapport d'erreur Market comporte une section `` Plateforme '', contenant parfois la version Android de l'appareil.
Nikolay Elenkov

Réponses:

720

Veuillez vérifier ma réponse ici . En gros, je devais juste:

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

Ne faites pas l'appel à super()la saveInstanceStateméthode. C'était gâcher les choses ...

Il s'agit d'un bogue connu dans le package de support.

Si vous devez enregistrer l'instance et ajouter quelque chose à votre, outState Bundlevous pouvez utiliser ce qui suit:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

En fin de compte, la bonne solution était (comme on le voit dans les commentaires) d'utiliser:

transaction.commitAllowingStateLoss();

lors de l'ajout ou de l'exécution de la FragmentTransactioncause de la Exception.

Ovidiu Latcu
la source
370
Vous devez utiliser commitAllowingStateLoss () au lieu de commit ()
meh
18
Ce commentaire sur commitAllowingStateLoss () est une réponse à part entière - vous devez le poster comme ça.
Risadinha
20
Concernant 'commitAllowingStateLoss' - /> "Ceci est dangereux car le commit peut être perdu si l'activité doit être restaurée ultérieurement à partir de son état, donc cela ne devrait être utilisé que dans les cas où il est acceptable que l'état de l'interface utilisateur change de façon inattendue le l'utilisateur."
Codeversed
10
Si je regarde la source v4, popBackStackImmediateelle échoue immédiatement si l'état a été enregistré. Auparavant, l'ajout du fragment avec commitAllowingStateLossne joue aucun rôle. Mes tests montrent que cela est vrai. Il n'a aucun effet sur cette exception spécifique. Ce dont nous avons besoin, c'est d'une popBackStackImmediateAllowingStateLossméthode.
Synesso
3
@DanieleB oui, j'ai posté une réponse ici. Mais j'ai en fait trouvé une solution encore meilleure en utilisant le bus de messages Otto: enregistrez le fragment en tant qu'abonné et écoutez le résultat asynchrone du bus. Se désinscrire à la pause et se réinscrire à la reprise. L'async a également besoin d'une méthode Produce pour les moments où il se termine et le fragment est suspendu. Lorsque j'aurai le temps, je mettrai à jour ma réponse avec plus de détails.
Synesso
130

Il existe de nombreux problèmes associés avec un message d'erreur similaire. Vérifiez la deuxième ligne de cette trace de pile particulière. Cette exception est spécifiquement liée à l'appel à FragmentManagerImpl.popBackStackImmediate.

Cet appel de méthode, comme popBackStack, échouera toujoursIllegalStateException si l'état de session a déjà été enregistré. Vérifiez la source. Vous ne pouvez rien faire pour empêcher cette exception d'être levée.

  • La suppression de l'appel à super.onSaveInstanceStaten'aidera pas.
  • La création du fragment avec commitAllowingStateLossn'aidera pas.

Voici comment j'ai observé le problème:

  • Il y a un formulaire avec un bouton d'envoi.
  • Lorsque vous cliquez sur le bouton, une boîte de dialogue est créée et un processus asynchrone démarre.
  • L'utilisateur clique sur la touche d'accueil avant la fin du processus - onSaveInstanceStateest appelé.
  • Le processus se termine, un rappel est effectué et popBackStackImmediateest tenté.
  • IllegalStateException Est lancé.

Voici ce que j'ai fait pour le résoudre:

Comme il n'est pas possible d'éviter le IllegalStateExceptiondans le rappel, rattrapez-le et ignorez-le.

try {
    activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
    // There's no way to avoid getting this if saveInstanceState has already been called.
}

Cela suffit pour empêcher l'application de planter. Mais maintenant, l'utilisateur va restaurer l'application et voir que le bouton sur lequel il pensait avoir appuyé n'a pas du tout été pressé (ils pensent). Le fragment de formulaire est toujours visible!

Pour résoudre ce problème, lorsque la boîte de dialogue est créée, créez un état pour indiquer que le processus a commencé.

progressDialog.show(fragmentManager, TAG);
submitPressed = true;

Et enregistrez cet état dans le bundle.

@Override
public void onSaveInstanceState(Bundle outState) {
    ...
    outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}

N'oubliez pas de le recharger à nouveau dans onViewCreated

Ensuite, lors de la reprise, annulez les fragments si la soumission a été tentée précédemment. Cela empêche l'utilisateur de revenir à ce qui semble être un formulaire non soumis.

@Override
public void onResume() {
    super.onResume();
    if (submitPressed) {
        // no need to try-catch this, because we are not in a callback
        activity.getSupportFragmentManager().popBackStackImmediate(name);
        submitPressed = false;
    }
}
Synesso
la source
5
Lecture intéressante à ce sujet ici: androiddesignpatterns.com/2013/08/…
Pascal
Si vous utilisez DialogFragment, j'en ai fait une alternative ici: github.com/AndroidDeveloperLB/DialogShard
développeur Android
Et si le popBackStackImmediatetéléphone était appelé par Android lui-même?
Kimi Chiu
1
Absolument génial. Celui-ci devrait être la réponse acceptée. Merci beaucoup! Peut-être que j'ajouterais submitPressed = false; après popBackStackInmediate.
Neonigma
Je n'ai pas utilisé la méthode public void onSaveInstanceState (Bundle outState). Dois-je définir une méthode vide pour public void onSaveInstanceState (Bundle outState)?
Faxriddin Abdullayev
55

Vérifiez si l'activité isFinishing()avant de montrer le fragment et faites attention à commitAllowingStateLoss().

Exemple:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Naskov
la source
1
! isFinishing () &&! isDestroyed () ne fonctionne pas pour moi.
Allen Vork
! isFinishing () &&! isDestroyed () a fonctionné pour moi, mais il nécessite l'API 17. Mais il n'affiche tout simplement pas DialogFragment. Voir stackoverflow.com/questions/15729138/… pour d'autres bonnes solutions, stackoverflow.com/a/41813953/2914140 m'a aidé.
CoolMind
29

Nous sommes en octobre 2017 et Google crée la bibliothèque de support Android avec les nouvelles choses appelées composant Lifecycle. Il fournit une nouvelle idée de ce problème «Impossible d'exécuter cette action après onSaveInstanceState».

En bref:

  • Utilisez le composant de cycle de vie pour déterminer s'il est temps de faire apparaître votre fragment.

Version plus longue avec explication:

  • pourquoi ce problème est-il apparu?

    C'est parce que vous essayez d'utiliser FragmentManagervotre activité (qui va contenir votre fragment je suppose?) Pour valider une transaction pour votre fragment. Habituellement, cela donnerait l'impression que vous essayez de faire une transaction pour un fragment à venir, tandis que l'activité de l'hôte appelle déjà la savedInstanceStateméthode (l'utilisateur peut arriver de toucher le bouton d'accueil, donc l'activité appelle onStop(), dans mon cas, c'est la raison)

    Habituellement, ce problème ne devrait pas se produire - nous essayons toujours de charger le fragment dans l'activité au tout début, comme la onCreate()méthode est un endroit parfait pour cela. Mais parfois, cela se produit , surtout lorsque vous ne pouvez pas décider quel fragment vous allez charger pour cette activité, ou que vous essayez de charger un fragment à partir d'un AsyncTaskbloc (ou quoi que ce soit prendra un peu de temps). Le temps, avant que la transaction de fragment ne se produise réellement, mais après la onCreate()méthode de l'activité , l'utilisateur peut tout faire. Si l'utilisateur appuie sur le bouton d'accueil, ce qui déclenche la onSavedInstanceState()méthode de l'activité , il y aurait un can not perform this actioncrash.

    Si besoin de quelqu'un pour voir plus loin dans cette question, je leur suggère de jeter un oeil à ce blog post . Il semble profondément à l'intérieur de la couche de code source et explique beaucoup de choses à ce sujet. En outre, cela donne la raison pour laquelle vous ne devez pas utiliser la commitAllowingStateLoss()méthode pour contourner ce crash (croyez-moi, cela n'offre rien de bon pour votre code)

  • Comment régler ceci?

    • Dois-je utiliser une commitAllowingStateLoss()méthode pour charger un fragment? Non, vous ne devriez pas ;

    • Dois-je remplacer la onSaveInstanceStateméthode, ignorer la superméthode à l'intérieur? Non, vous ne devriez pas ;

    • Dois-je utiliser l' isFinishingactivité magique interne pour vérifier si l'activité hôte est au bon moment pour la transaction de fragment? Oui, cela ressemble à la bonne façon de faire.

  • Jetez un œil à ce que le composant Lifecycle peut faire.

    Fondamentalement, Google effectue une implémentation à l'intérieur de la AppCompatActivityclasse (et plusieurs autres classes de base que vous devez utiliser dans votre projet), ce qui facilite la détermination de l'état actuel du cycle de vie . Revenons à notre problème: pourquoi ce problème se produirait-il? C'est parce que nous faisons quelque chose au mauvais moment. Nous essayons donc de ne pas le faire, et ce problème aura disparu.

    Je code un peu pour mon propre projet, voici ce que je fais en utilisant LifeCycle. Je code en Kotlin.

val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.

fun dispatchFragment(frag: Fragment) {
    hostActivity?.let {
       if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
           showFragment(frag)
       }
    }
}

private fun showFragment(frag: Fragment) {
    hostActivity?.let {
        Transaction.begin(it, R.id.frag_container)
                .show(frag)
                .commit()
    }

Comme je le montre ci-dessus. Je vais vérifier l'état du cycle de vie de l'activité hôte. Avec le composant Lifecycle dans la bibliothèque de support, cela pourrait être plus spécifique. Le code lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)signifie, si l'état actuel est au moins onResume, au plus tard? Ce qui garantit que ma méthode ne sera pas exécutée pendant un autre état de vie (comme onStop).

  • Est-ce que tout est fait?

    Bien sûr que non. Le code que j'ai montré indique une nouvelle façon d'empêcher l'application de planter. Mais si elle passe à l'état de onStop, cette ligne de code ne fera rien et ne montrera donc rien sur votre écran. Lorsque les utilisateurs reviennent à l'application, ils verront un écran vide, c'est l'activité de l'hôte vide ne montrant aucun fragment du tout. C'est une mauvaise expérience (ouais un peu mieux qu'un crash).

    Donc, ici, je souhaite qu'il puisse y avoir quelque chose de plus agréable: l'application ne se bloquera pas si elle arrive à l'état de vie plus tard onResume, la méthode de transaction est consciente de l'état de vie; en outre, l'activité tentera de continuer à terminer cette action de transaction de fragment, une fois que l'utilisateur sera revenu sur notre application.

    J'ajoute quelque chose de plus à cette méthode:

class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
    private val hostActivity: FragmentActivity? = _host
    private val lifeCycle: Lifecycle? = _host.lifecycle
    private val profilePendingList = mutableListOf<BaseFragment>()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun resume() {
        if (profilePendingList.isNotEmpty()) {
            showFragment(profilePendingList.last())
        }
    }

    fun dispatcherFragment(frag: BaseFragment) {
        if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
            showFragment(frag)
        } else {
            profilePendingList.clear()
            profilePendingList.add(frag)
        }
    }

    private fun showFragment(frag: BaseFragment) {
        hostActivity?.let {
            Transaction.begin(it, R.id.frag_container)
                    .show(frag)
                    .commit()
        }
    }
}

Je maintiens une liste à l'intérieur de cette dispatcherclasse, pour stocker ces fragments, je n'ai pas la chance de terminer l'action de transaction. Et lorsque l'utilisateur revient de l'écran d'accueil et qu'il y a encore un fragment en attente d'être lancé, il ira à la resume()méthode sous l' @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)annotation. Maintenant, je pense que cela devrait fonctionner comme je m'y attendais.

Anthonyeef
la source
8
Ce serait bien d'utiliser Java au lieu de Kotlin
Shchvova
1
Pourquoi votre implémentation FragmentDispatcherutilise-t-elle une liste pour stocker les fragments en attente s'il n'y a qu'un seul fragment restauré?
fraherm
21

Voici une solution différente à ce problème.

En utilisant une variable membre privée, vous pouvez définir les données renvoyées comme une intention qui peut ensuite être traitée après super.onResume ();

Ainsi:

private Intent mOnActivityResultIntent = null; 

@Override
protected void onResume() {
    super.onResume();
    if(mOnActivityResultIntent != null){
        ... do things ...
        mOnActivityResultIntent = null;
    }
 }

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
    if(data != null){
        mOnActivityResultIntent = data;
    }
}
Jed
la source
7
Selon l'action que vous faisiez qui n'était pas autorisée, cela pourrait nécessiter de passer à un moment encore plus tard que onResume (). Pour l'insatcne, si FragmentTransaction.commit () est le problème, cela doit aller dans onPostResume () à la place.
pjv
1
C'est LA réponse à cette question pour moi. Étant donné que je devais transmettre une balise NFC reçue à l'activité précédente, c'est ce que j'ai fait pour moi.
Janis Peisenieks
7
Pour moi, cela arrivait parce que je n'appelais pas super.onActivityResult().
Sufian
20

Solution courte et fonctionnelle:

Suivez des étapes simples

Pas

Étape 1: remplacer l' onSaveInstanceStateétat dans le fragment respectif. Et supprimez la super méthode.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Étape 2: utiliser fragmentTransaction.commitAllowingStateLoss( );

au lieu de fragmentTransaction.commit( ); tandis que les opérations de fragment.

Vinayak
la source
Formulaire de réponse n'est pas copié ou arbitré ailleurs où .est a été affecté à l' aide aux gens de ma solution de travail qui est obtenu par plusieurs essais et erreurs
Vinayak
12

ATTENTION , l'utilisation transaction.commitAllowingStateLoss()peut entraîner une mauvaise expérience pour l'utilisateur. Pour plus d'informations sur les raisons de la levée de cette exception, consultez cet article .

Eric Brandwein
la source
6
Cela ne fournit pas de réponse à la question, vous devez fournir une réponse valide à la question
Umar Ata
10

J'ai trouvé une sale solution pour ce genre de problème. Si vous voulez toujours garder votre ActivityGroupspour une raison quelconque (j'avais des raisons de limitation de temps), vous implémentez simplement

public void onBackPressed() {}

dans votre Activityet faites un peu de backcode là-dedans. même s'il n'y a pas une telle méthode sur les appareils plus anciens, cette méthode est appelée par les plus récents.

saberrider
la source
6

N'utilisez pas commitAllowingStateLoss (), il ne doit être utilisé que dans les cas où il est acceptable que l'état de l'interface utilisateur change de façon inattendue sur l'utilisateur.

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss ()

Si la transaction se produit dans ChildFragmentManager de parentFragment, utilisez parentFragment.isResume () extérieur pour vérifier à la place.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
Chandler
la source
5

J'ai eu un problème similaire, le scénario était le suivant:

  • Mon activité consiste à ajouter / remplacer des fragments de liste.
  • Chaque fragment de liste a une référence à l'activité, pour notifier l'activité lorsqu'un élément de liste est cliqué (modèle d'observateur).
  • Chaque fragment de liste appelle setRetainInstance (true); dans sa méthode onCreate .

La méthode onCreate de l' activité était la suivante:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }

L'exception a été levée parce que lorsque la configuration change (appareil en rotation), l'activité est créée, le fragment principal est récupéré de l'historique du gestionnaire de fragments et en même temps le fragment a déjà une VIEILLE référence à la activité détruite

changer l'implémentation en ceci a résolu le problème:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }
        mMainFragment.setOnSelectionChangedListener(this);

vous devez définir vos écouteurs chaque fois que l'activité est créée pour éviter que les fragments aient des références à d'anciennes instances détruites de l'activité.

Mina Samy
la source
5

Si vous héritez de FragmentActivity, vous devez appeler la superclasse dans onActivityResult():

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

Si vous ne faites pas cela et essayez d'afficher une boîte de dialogue de fragment dans cette méthode, vous pouvez obtenir des OP IllegalStateException. (Pour être honnête, je ne comprends pas très bien pourquoi le super appel résout le problème. onActivityResult()Est appelé avant onResume(), donc il ne devrait toujours pas être autorisé à afficher une boîte de dialogue de fragment.)

Lawrence Kesteloot
la source
1
J'adorerais savoir pourquoi cela résout le problème.
Big McLargeHuge
3

J'obtenais cette exception lorsque j'appuyais sur le bouton de retour pour annuler le sélecteur d'intention sur l'activité de mon fragment de carte. J'ai résolu ce problème en remplaçant le code de onResume (où j'initialisais le fragment) par onstart () et l'application fonctionne correctement. J'espère que cela aide.

DCS
la source
2

Je pense que l'utilisation transaction.commitAllowingStateLoss();n'est pas la meilleure solution. Cette exception sera levée lorsque la configuration de l'activité a changé et fragmentéonSavedInstanceState() est appelé, puis votre méthode de rappel asynchrone essaie de valider le fragment.

Une solution simple pourrait être de vérifier si l'activité change de configuration ou non

par exemple vérifier isChangingConfigurations()

c'est à dire

if(!isChangingConfigurations()) { //commit transaction. }

Consultez également ce lien

Amol Desai
la source
D'une manière ou d'une autre, j'ai eu cette exception lorsque l'utilisateur clique sur quelque chose (le clic est le déclencheur pour effectuer la transaction-commit). Comment est-ce possible? Souhaitez-vous votre solution ici ici?
développeur Android
@androiddeveloper que faites-vous d'autre sur le clic de l'utilisateur. en quelque sorte, le fragment enregistre son état avant de valider la transaction
Amol Desai
L'exception a été levée sur la ligne exacte de validation de transaction. De plus, j'avais une faute de frappe bizarre: au lieu de "ici ici", je voulais dire "travailler ici".
développeur Android
@androiddeveloper vous avez raison! mais avant de valider la transaction, générez-vous un thread d'arrière-plan ou quelque chose?
Amol Desai
Je ne pense pas (désolé, je suis absent du bureau), mais pourquoi est-ce important? Ce sont toutes des choses d'interface utilisateur ici ... Si je fais quelque chose sur un fil d'arrière-plan, j'aurais des exceptions là-bas, et je ne mets pas de choses liées à l'interface utilisateur sur les fils d'arrière-plan, car c'est trop risqué.
développeur Android
2

La solution la plus simple et la plus simple que j'ai trouvée dans mon cas était probablement d'éviter de faire sauter le fragment incriminé de la pile en réponse au résultat de l'activité. Donc, changer cet appel dans mon onActivityResult():

popMyFragmentAndMoveOn();

pour ça:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        popMyFragmentAndMoveOn();
    }
}

aidé dans mon cas.

mojuba
la source
2

Si vous effectuez une FragmentTransaction dans onActivityResult, ce que vous pouvez faire, vous pouvez définir une valeur booléenne dans onActivityResult, puis dans onResume, vous pouvez effectuer votre FragmentTransaction sur la base de la valeur booléenne. Veuillez vous référer au code ci-dessous.

@Override
protected void onResume() {
    super.onResume;
    if(isSwitchFragment){
        isSwitchFragment=false;
        bottomNavigationView.getTabAt(POS_FEED).select();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
        isSwitchFragment=true;
    }
}
anoopbryan2
la source
Veuillez ne pas publier le code sous forme d'image, utilisez plutôt la mise en forme du code.
Micer
1
Oui, cela m'a aidé .., c'est la solution exacte et appropriée pour le dispositif de guimauve j'ai obtenu cette exception et résolu avec cette astuce simple .. !! D'où le vote.
sandhya sasane
2

Courtoisie: Solution pour IllegalStateException

Ce problème m'ennuyait depuis longtemps mais heureusement, je suis venu avec une solution concrète. Une explication détaillée est ici .

L'utilisation de commitAllowStateloss () pourrait empêcher cette exception mais entraînerait des irrégularités d'interface utilisateur.Jusqu'à présent, nous avons compris que IllegalStateException se produit lorsque nous essayons de valider un fragment après la perte de l'état Activity - nous devons donc simplement retarder la transaction jusqu'à ce que l'état soit restauré. .Cela peut être fait simplement comme ceci

Déclarez deux variables booléennes privées

 public class MainActivity extends AppCompatActivity {

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

Maintenant, dans onPostResume () et onPause, nous définissons et désactivons notre variable booléenne isTransactionSafe. L'idée est de marquer la trasnsaction en toute sécurité uniquement lorsque l'activité est au premier plan, de sorte qu'il n'y a aucune chance de perte d'état.

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume(){
    super.onPostResume();
    isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause(){
    super.onPause();
    isTransactionSafe=false;

}

private void commitFragment(){
    if(isTransactionSafe) {
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    }
}

-Ce que nous avons fait jusqu'à présent permettra d'économiser de IllegalStateException mais nos transactions seront perdues si elles sont effectuées après que l'activité se déplace en arrière-plan, un peu comme commitAllowStateloss (). Pour vous aider, nous avons la variable booléenne isTransactionPending

public void onPostResume(){
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) {
      commitFragment();
   }
}


private void commitFragment(){

 if(isTransactionSafe) {
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 }else {
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 }
}
IrshadKumail
la source
2

Les transactions de fragments ne doivent pas être exécutées après Activity.onStop()! Vérifiez que vous n'avez aucun rappel qui pourrait exécuter la transaction après onStop(). Il est préférable de corriger la raison au lieu d'essayer de contourner le problème avec des approches comme.commitAllowingStateLoss()

Ivo Stoyanov
la source
1

À partir de la bibliothèque de support version 24.0.0, vous pouvez appeler la FragmentTransaction.commitNow()méthode qui valide cette transaction de manière synchrone au lieu d'appeler commit()suivi de executePendingTransactions(). Comme le dit la documentation , cette approche est encore meilleure:

L'appel de commitNow est préférable à l'appel de commit () suivi de executePendingTransactions () car ce dernier aura pour effet secondaire de tenter de valider toutes les transactions actuellement en attente, que ce soit le comportement souhaité ou non.

Volodymyr
la source
1

Chaque fois que vous essayez de charger un fragment dans votre activité, assurez-vous que l'activité est en cours de reprise et ne va pas se mettre en pause. Dans un état de pause, vous risquez de perdre l'opération de validation effectuée.

Vous pouvez utiliser transaction.commitAllowingStateLoss () au lieu de transaction.commit () pour charger le fragment

ou

Créez un booléen et vérifiez si l'activité ne va pas être interrompue

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

puis lors du chargement de la vérification des fragments

if(mIsResumed){
//load the your fragment
}
Sharath kumar
la source
1

Pour contourner ce problème, nous pouvons utiliser le composant d'architecture de navigation , qui a été introduit dans Google I / O 2018. Le composant d'architecture de navigation simplifie la mise en œuvre de la navigation dans une application Android.

Levon Petrosyan
la source
Ce n'est pas assez bon et bogué (mauvaise gestion des liens profonds, ne peut pas enregistrer les fragments d'affichage / masquage de l'état et un problème important qui est toujours ouvert)
do01
1

En ce qui concerne @Anthonyeef grande réponse, voici un exemple de code en Java:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
        showFragment();
    } else {
        shouldShowFragmentInOnResume = true;
    }
}

private void showFragment() {
    //Your code here
}

@Override
protected void onResume() {
    super.onResume();

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}
MorZa
la source
1

Si vous avez un crash avec la méthode popBackStack () ou popBackStackImmediate (), veuillez essayer fixt avec:

        if (!fragmentManager.isStateSaved()) {
            fragmentManager.popBackStackImmediate();
        }

Cela fonctionne aussi pour moi.

Tapa Save
la source
Notez qu'il nécessite une API 26 et supérieure
Itay Feldman
1

Dans mon cas, j'ai eu cette erreur dans une méthode de remplacement appelée onActivityResult. Après avoir creusé, je viens de comprendre que je devais peut-être appeler « super » avant.
Je l'ai ajouté et ça a juste fonctionné

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
    if (resultCode == Activity.RESULT_OK && requestCode == 0) {
        mostrarFragment(FiltroFragment.newInstance())
    }

}

Peut-être que vous avez juste besoin d'ajouter un «super» sur tout remplacement que vous faites avant votre code.

Gian Gomen
la source
1

Extension Kotlin

fun FragmentManager?.replaceAndAddToBackStack(
    @IdRes containerViewId: Int,
    fragment: () -> Fragment,
    tag: String
) {
    // Find and synchronously remove a fragment with the same tag.
    // The second transaction must start after the first has finished.
    this?.findFragmentByTag(tag)?.let {
        beginTransaction().remove(it).commitNow()
    }
    // Add a fragment.
    this?.beginTransaction()?.run {
        replace(containerViewId, fragment, tag)
        // The next line will add the fragment to a back stack.
        // Remove if not needed.
        // You can use null instead of tag, but tag is needed for popBackStack(), 
        // see https://stackoverflow.com/a/59158254/2914140
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

Usage:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
CoolMind
la source
Vous pouvez supprimer le () -> avant Fragment
Claire
@Claire, merci! Voulez-vous dire de changer pour fragment: Fragment? Oui, j'ai essayé cette variante, mais dans ce cas, un fragment serait créé dans tous les cas (même lorsque fragmentManager == null, mais je n'ai pas rencontré cette situation). J'ai mis à jour la réponse et changé null pour ajouter un tag addToBackStack().
CoolMind
0

Ajoutez ceci dans votre activité

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (outState.isEmpty()) {
        // Work-around for a pre-Android 4.2 bug
        outState.putBoolean("bug:fix", true);
    }
}
yifan
la source
0

J'ai également rencontré ce problème et un problème se produit chaque fois que le contexte de votre FragmentActivityest modifié (par exemple, l'orientation de l'écran est modifiée, etc.). La meilleure solution consiste donc à mettre à jour le contexte à partir de votre FragmentActivity.

Adam
la source
0

J'ai fini par créer un fragment de base et faire en sorte que tous les fragments de mon application l'étendent

public class BaseFragment extends Fragment {

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }
}

Ensuite, lorsque j'essaie de montrer un fragment que j'utilise showAllowingStateLossau lieu deshow

comme ça:

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

Je suis venu à cette solution à partir de ce PR: https://github.com/googlesamples/easypermissions/pull/170/files

Ahmad El-Melegy
la source
0

Une autre solution de contournement possible, dont je ne sais pas si cela aide dans tous les cas (origine ici ):

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}
développeur android
la source
0

Je sais qu'il y a une réponse acceptée par @Ovidiu Latcu mais après un certain temps, l'erreur persiste.

@Override
protected void onSaveInstanceState(Bundle outState) {
     //No call for super(). Bug on API Level > 11.
}

Crashlytics m'envoie toujours ce message d'erreur étrange.

Cependant, l'erreur ne se produit maintenant que sur la version 7+ (Nougat) Mon correctif consistait à utiliser commitAllowingStateLoss () au lieu de commit () au fragmentTransaction.

Ce message est utile pour commitAllowingStateLoss () et n'a plus jamais eu de problème de fragment.

Pour résumer, la réponse acceptée ici pourrait fonctionner sur les versions Android pré Nougat.

Cela pourrait faire économiser quelques heures à quelqu'un. codages heureux. <3 acclamations

ralphgabb
la source