Sur l'affichage de la boîte de dialogue, j'obtiens "Impossible d'exécuter cette action après onSaveInstanceState"

121

Certains utilisateurs signalent, s'ils utilisent l'action rapide dans la barre de notification, ils obtiennent une fermeture forcée.

Je montre une action rapide dans la notification qui appelle la classe "TestDialog" . Dans la classe TestDialog après avoir appuyé sur le bouton "snooze", je vais afficher le SnoozeDialog.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

L'erreur est *IllegalStateException: Can not perform this action after onSaveInstanceState*.

La ligne de code où l'exception IllegarStateException est déclenchée est:

snoozeDialog.show(fm, "snooze_dialog");

La classe étend "FragmentActivity" et la classe "SnoozeDialog" étend "DialogFragment".

Voici la trace de pile complète de l'erreur:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

Je ne peux pas reproduire cette erreur, mais je reçois beaucoup de rapports d'erreur.

Quelqu'un peut-il m'aider, comment puis-je corriger cette erreur?

chrisonline
la source
2
Avez-vous trouvé une solution? J'ai le même problème que vous. J'ai demandé ici: stackoverflow.com/questions/15730878/… Veuillez vérifier ma question et voir la solution possible qui ne fonctionne pas pour mon cas. Peut-être que cela fonctionnera pour vous.
rootpanthera
Pas encore de solution :-( Et votre suggestion est déjà ajoutée à ma classe.
chrisonline
Vérifiez la réponse acceptée à partir d'ici. Cela a résolu mon problème: stackoverflow.com/questions/14177781/…
bogdan
4
Votre activité est-elle visible lorsque cette boîte de dialogue est déclenchée? Cela semble être dû au fait que votre application tente d'afficher une boîte de dialogue associée à une activité qui a été mise en pause / arrêtée.
Kai
stackoverflow.com/questions/7575921/ ... vous avez essayé ceci Im sûr.
Orion

Réponses:

66

C'est un problème courant . Nous avons résolu ce problème en remplaçant show () et en gérant l'exception dans la classe étendue DialogFragment

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

Notez que l'application de cette méthode ne modifiera pas les champs internes de DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

Cela peut conduire à des résultats inattendus dans certains cas. Mieux vaut utiliser commitAllowingStateLoss () au lieu de commit ()

Rafael
la source
3
Mais pourquoi ce problème se produit-il? Est-il correct d'ignorer l'erreur? Que se passe-t-il quand vous le faites? Après tout, en cliquant, cela signifie que l'activité est en direct et bien ... Quoi qu'il en soit, j'en ai signalé ici car je considère cela comme un bug: code.google.com/p/android/issues/detail?id= 207269
développeur Android
1
Pouvez-vous s'il vous plaît jouer ici et / ou commenter?
développeur android
2
il vaut mieux appeler super.show (manager, tag) dans la clause try-catch. Les drapeaux appartenant à DialogFragment peuvent rester en sécurité de cette façon
Shayan_Aryan
20
À ce stade, vous pouvez appeler commitAllowingStateLoss () au lieu de commit (). L'exception ne serait pas soulevée.
ARLabs
1
@ARLabs J'imagine que dans ce cas, ce serait mieux pour le répondeur aussi car si vous attrapez simplement l'exception comme indiqué ici, la boîte de dialogue ne sera certainement pas affichée. Mieux vaut afficher la boîte de dialogue si vous le pouvez et elle pourrait simplement disparaître si l'état doit être restauré. Gardez également l'utilisation de la mémoire de votre application faible en arrière-plan afin qu'elle ne soit pas susceptible d'être détruite.
androidguy
27

Cela signifie que vous commit()( show()dans le cas de DialogFragment) fragmentez après onSaveInstanceState().

Android enregistrera votre état de fragment à onSaveInstanceState(). Donc, si vous commit()fragmentez après, l' onSaveInstanceState()état du fragment sera perdu.

En conséquence, si l'activité est supprimée et recrée plus tard, le fragment ne s'ajoutera pas à l'activité, ce qui est une mauvaise expérience utilisateur. C'est pourquoi Android ne permet pas à tout prix la perte d'état.

La solution simple est de vérifier si l'état est déjà enregistré.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

Remarque: onResumeFragments () appellera lorsque les fragments reprendront.

Pongpat
la source
1
Que faire si je souhaite afficher le DialogFragment dans un autre fragment?
développeur android
Notre solution est de créer une activité et de fragmenter la classe de base et de déléguer onResumeFragments à fragmenter (nous créons onResumeFragments dans la classe de base de fragment). Ce n'est pas une bonne solution mais cela fonctionne. Si vous avez une meilleure solution, faites-le moi savoir :)
Pongpat
Eh bien, j'ai pensé que l'affichage de la boîte de dialogue dans "onStart" devrait fonctionner correctement, car le fragment est sûrement affiché, mais je vois toujours des rapports de plantage à ce sujet. J'ai été chargé d'essayer de le mettre sur le "onResume" à la place. A propos des alternatives, j'ai vu ceci: twigstechtips.blogspot.co.il/2014/01/… , mais c'est assez bizarre.
développeur android
Je pense que la raison pour laquelle twigstechtips.blogspot.co.il/2014/01/ ... fonctionne parce qu'il démarre un nouveau thread et donc tout le code du cycle de vie, c'est-à-dire onStart, onResume, etc. appelé avant que le code runOnUiThread ne s'exécute. Cela signifie que l'état est déjà restauré avant l'appel de runOnUiThread.
Pongpat
2
J'utilise un seul appel pour publier (exécutable). En ce qui concerne getFragmentManager, cela dépend. Si vous souhaitez partager ce dialogue avec une autre activité, vous devez utiliser getFragmentManager, cependant, si cette boîte de dialogue n'existe qu'avec le fragment, getChildFragmentManager semble un meilleur choix.
Pongpat
16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

ref: lien

huu duy
la source
11

Après quelques jours , je veux partager ma solution que je l' ai fixé, de le montrer DialogFragment vous devriez passer outre la show()méthode de celui - ci et appeler commitAllowingStateLoss()sur l' Transactionobjet. Voici un exemple à Kotlin:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }
Dennis Zinkovski
la source
1
Alors que les développeurs ne doivent pas hériter de DialogFragmentvous pourriez changer cela pour être une fonction d'extension Kotlin avec la signature suivante: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). De plus, le try-catch n'est pas nécessaire puisque vous appelez la commitAllowingStateLoss()méthode et non la commit()méthode.
Adil Hussain
10

Si la boîte de dialogue n'est pas vraiment importante (vous pouvez ne pas l'afficher lorsque l'application est fermée / n'est plus visible), utilisez:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

Et ouvrez votre boîte de dialogue (fragment) uniquement lorsque nous sommes en cours d'exécution:

if (running) {
    yourDialog.show(...);
}

EDIT, PROBABLEMENT MEILLEURE SOLUTION:

Là où onSaveInstanceState est appelé dans le cycle de vie est imprévisible, je pense qu'une meilleure solution consiste à vérifier isSavedInstanceStateDone () comme ceci:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

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

    savedInstanceStateDone = false;
}

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

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}
Franc
la source
Cela ne semble pas fonctionner, car j'obtiens cette exception sur l'appel de la méthode «onStart» (en essayant d'afficher le DialogFragment là-bas).
développeur android
Tu as sauvé ma journée. Merci Frank.
Cüneyt le
7

Je suis confronté à ce problème depuis des années.
Les internets sont jonchés de dizaines (des centaines? Des milliers?) De discussions à ce sujet, et la confusion et la désinformation y semblent abondantes.
Pour aggraver la situation, et dans l'esprit de la bande dessinée xkcd "14 standards", je jette ma réponse sur le ring.
normes xkcd 14

Le cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), et des solutions similaires semblent tout atroce.

Espérons que ce qui suit montre facilement comment reproduire et résoudre le problème:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};
swooby
la source
2
J'aime les gens qui votent sans explication. Au lieu de simplement voter par opposition, il serait peut-être préférable qu'ils expliquent en quoi ma solution est défectueuse? Puis-je voter contre un vote contre un vote négatif?
swooby
1
Oui, c'est un problème de SO, j'écris ce problème à chaque fois dans des suggestions, mais ils ne veulent pas le résoudre.
CoolMind
2
Je pense que les votes négatifs peuvent être le résultat du XKCD intégré, les réponses ne sont vraiment pas l'endroit idéal pour les commentaires sociaux (aussi amusants et / ou vrais).
RestingRobot
6

essayez d'utiliser FragmentTransaction au lieu de FragmentManager. Je pense que le code ci-dessous résoudra votre problème. Sinon, faites-le moi savoir.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

ÉDITER:

Transaction de fragment

Veuillez vérifier ce lien. Je pense que cela résoudra vos questions.

RV RIJO
la source
4
Toute explication sur les raisons pour lesquelles l'utilisation de FragmentTransaction résout le problème serait excellente.
Hemanshu
3
Dialog # show (FragmentManager, tag) fait la même chose. Ce n'est pas une solution.
William
3
Cette réponse n'est pas la solution. DialogFragment # show (ft) et show (fm) font exactement la même chose.
danijoo
@danijoo Vous avez raison de dire que les deux font le même travail. Mais dans quelques téléphones, il existe un problème similaire à celui-ci si vous utilisez fragmentmanager au lieu de fragmenttransaction. Donc, dans mon cas, cela a résolu mon problème.
RIJO RV
6

L'utilisation des nouvelles étendues de cycle de vie d'Activity-KTX est aussi simple que l'exemple de code suivant:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Cette méthode peut être directement appelée après onStop () et affichera avec succès la boîte de dialogue une fois que onResume () a été appelé au retour.

PingouinDan
la source
3

De nombreuses vues publient des événements de haut niveau tels que des gestionnaires de clics dans la file d'attente d'événements pour une exécution différée. Le problème est donc que "onSaveInstanceState" a déjà été appelé pour l'activité mais que la file d'attente d'événements contient un "événement de clic" différé. Par conséquent, lorsque cet événement est envoyé à votre gestionnaire

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

et votre code fait, showl'exception IllegalStateException est levée.

La solution la plus simple consiste à nettoyer la file d'attente d'événements, en onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}
sim
la source
Avez-vous réellement confirmé que cela résout le problème?
mhsmith
Google l'a ajouté à la prochaine version des bibliothèques androidx, actuellement en version bêta ( activityet fragment).
mhsmith
1
@mhsmith Je me souviens que cette solution a résolu le problème dans mon code avec IllegalStateException
sim
2

Rendez votre objet de fragment de boîte de dialogue global et appelez rejeterAllowingStateLoss () dans la méthode onPause ()

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

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

N'oubliez pas d'attribuer une valeur dans le fragment et d'appeler show () en cliquant sur le bouton ou ailleurs.

Rohit Rajpal
la source
1

Bien que ce ne soit officiellement mentionné nulle part, j'ai été confronté à ce problème plusieurs fois. D'après mon expérience, il y a quelque chose qui ne va pas dans la bibliothèque de compatibilité prenant en charge des fragments sur des plates-formes plus anciennes qui provoque ce problème. Vous utilisez le test en utilisant l'API normale du gestionnaire de fragments. Si rien ne fonctionne, vous pouvez utiliser la boîte de dialogue normale au lieu du fragment de boîte de dialogue.

Dalvinder Singh
la source
1
  1. Ajoutez cette classe à votre projet: (doit être dans le package android.support.v4.app )
package android.support.v4.app;


/ **
 * Créé par Gil le 16/08/2017.
 * /

classe publique StatelessDialogFragment étend DialogFragment {
    / **
     * Afficher la boîte de dialogue, ajouter le fragment en utilisant une transaction existante puis valider le
     * transaction tout en permettant la perte d'état.
* * Je vous recommande d'utiliser {@link #show (FragmentTransaction, String)} la plupart du temps, mais * ceci est pour les dialogues dont vous ne vous souciez vraiment pas. (Débogage / Suivi / Annonces, etc.) * * Transaction @param * Une transaction existante dans laquelle ajouter le fragment. * Balise @param * La balise de ce fragment, selon * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Renvoie l'identifiant de la transaction validée, selon * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, chaîne) * / public int showAllowingStateLoss (transaction FragmentTransaction, balise String) { mDismissed = faux; mShownByMe = vrai; transaction.add (this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Afficher la boîte de dialogue, en ajoutant le fragment au FragmentManager donné. C'est une commodité * pour créer explicitement une transaction, y ajouter le fragment avec la balise donnée, et * le commettre sans se soucier de l'état. Cela n'ajoute pas la transaction au * pile arrière. Lorsque le fragment est rejeté, une nouvelle transaction sera exécutée pour le supprimer * de l'activité.
* * Je vous recommande d'utiliser {@link #show (FragmentManager, String)} la plupart du temps, mais c'est * pour les dialogues qui ne vous intéressent vraiment pas. (Débogage / Suivi / Annonces, etc.) * * * @param manager * Le FragmentManager auquel ce fragment sera ajouté. * Balise @param * La balise de ce fragment, selon * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (Gestionnaire de FragmentManager, balise String) { mDismissed = faux; mShownByMe = vrai; FragmentTransaction ft = manager.beginTransaction (); ft.add (this, tag); ft.commitAllowingStateLoss (); } }
  1. Étendre StatelessDialogFragment au lieu de DialogFragment
  2. Utilisez la méthode showAllowingStateLoss au lieu de show

  3. Prendre plaisir ;)

Gil SH
la source
À quoi servent tous ces champs booléens? Pourquoi ne sont-ils pas déclarés comme membres de classe?
undefined
1
Les champs booléens sont des membres protégés de DialogFragment, leurs noms suggèrent évidemment à quoi ils servent et nous devons les mettre à jour afin de ne pas interférer avec la logique de DialogFragment. Notez que dans la classe originale DialogFragment, ces fonctions existent mais sans accès public
Gil SH
Ough ces membres ne sont pas protégés, ils sont internes.J'obtenais des erreurs de compilation lorsque j'ai mis StatelessDialogFragmentdans l'un de mes packages.Merci mec, je vais le tester en production bientôt.
undefined
1

utiliser ce code

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

au lieu de

yourFragment.show(fm, "fragment_tag");
Faxriddin Abdullayev
la source
1

J'ai trouvé une solution élégante à ce problème en utilisant la réflexion. Le problème de toutes les solutions ci-dessus est que les champs mDismissed et mShownByMe ne changent pas d'état.

Remplacez simplement la méthode "show" dans votre propre fragment de dialogue de feuille de fond personnalisé comme l'exemple ci-dessous (Kotlin)

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }
Рома Богдан
la source
4
"J'ai trouvé une solution élégante à ce problème en utilisant la réflexion." comment est-ce élégant?
Mark Buikema
élégant, élégant, chic, intelligent, gentil, gracieux
Рома Богдан
1
c'est la seule solution qui a fonctionné pour moi. Je pense que c'est élégant
MBH
0

L'implémentation suivante peut être utilisée pour résoudre le problème de l'exécution en toute sécurité des changements d'état au cours du Activitycycle de vie, en particulier pour afficher les boîtes de dialogue: si l'état de l'instance a déjà été enregistré (par exemple en raison d'un changement de configuration), il les reporte jusqu'à ce que l'état repris ait été effectuée.

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

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

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Puis en utilisant une classe comme celle-ci:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

Vous pouvez afficher les boîtes de dialogue en toute sécurité sans vous soucier de l'état de l'application:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

puis appelez TestDialog.show(this)depuis votre XAppCompatActivity.

Si vous souhaitez créer une classe de dialogue plus générique avec des paramètres, vous pouvez les enregistrer dans un Bundleavec les arguments de la show()méthode et les récupérer avec getArguments()in onCreateDialog().

L'approche dans son ensemble peut sembler un peu complexe, mais une fois que vous avez créé les deux classes de base pour les activités et les dialogues, elle est assez facile à utiliser et fonctionne parfaitement. Il peut être utilisé pour d'autres Fragmentopérations basées qui pourraient être affectées par le même problème.

gicci
la source
0

Cette erreur semble se produire car les événements d'entrée (tels que les événements de touche enfoncée ou de clic) sont livrés après l' onSaveInstanceStateappel.

La solution consiste à remplacer onSaveInstanceStatevotre activité et à annuler tous les événements en attente.

@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();
        }
    }
}
William
la source