Quando si mostra la finestra di dialogo ottengo "Impossibile eseguire questa azione dopo onSaveInstanceState"


121

Alcuni utenti stanno segnalando, se utilizzano l'azione rapida nella barra di notifica, si stanno avvicinando forzatamente.

Mostro un'azione rapida nella notifica che chiama la classe "TestDialog" . Nella classe TestDialog dopo aver premuto il pulsante "snooze", mostrerò 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'errore è *IllegalStateException: Can not perform this action after onSaveInstanceState*.

La riga di codice in cui viene attivata IllegarStateException è:

snoozeDialog.show(fm, "snooze_dialog");

La classe sta estendendo "FragmentActivity" e la classe "SnoozeDialog" sta estendendo "DialogFragment".

Ecco la traccia completa dello stack dell'errore:

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)

Non riesco a riprodurre questo errore, ma ricevo molti rapporti di errore.

Qualcuno può aiutare che come posso correggere questo errore?


2
Hai trovato una soluzione? Ho il tuo stesso problema. Ho chiesto qui: stackoverflow.com/questions/15730878/… Si prega di controllare la mia domanda e vedere la possibile soluzione che non funziona per il mio caso. Forse funzionerà per te.
rootpanthera

Nessuna soluzione ancora :-( E il tuo suggerimento è già stato aggiunto alla mia classe.
Chrisonline

Controlla la risposta accettata da qui. Questo ha risolto il mio problema: stackoverflow.com/questions/14177781/…
bogdan

4
La tua attività è visibile quando viene attivata questa finestra di dialogo? Sembra che ciò possa essere causato dal tentativo della tua app di visualizzare una finestra di dialogo allegata a un'attività che è stata sospesa / interrotta.
Kai

stackoverflow.com/questions/7575921/… hai provato questo ne sono sicuro.
Orion

Risposte:


66

Questo è un problema comune . Abbiamo risolto questo problema sovrascrivendo show () e gestendo l'eccezione nella classe estesa 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);
        }
    }
}

Nota che l'applicazione di questo metodo non altererà i campi interni di DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

Ciò può portare a risultati imprevisti in alcuni casi. Meglio usare commitAllowingStateLoss () invece di commit ()


3
Ma perché si verifica questo problema? Va bene ignorare l'errore? Cosa succede quando lo fai? Dopotutto, quando si fa clic, significa che l'attività è in diretta e bene ... Ad ogni modo, l'ho segnalato qui perché lo considero un bug: code.google.com/p/android/issues/detail?id= 207269
sviluppatore Android

1
Puoi per favore recitare e / o commentare lì, quindi?
sviluppatore Android

2
è meglio chiamare super.show (manager, tag) all'interno della clausola try-catch. Le bandiere di proprietà di DialogFragment possono stare al sicuro in questo modo
Shayan_Aryan

20
A questo punto puoi chiamare commitAllowingStateLoss () invece di commit (). L'eccezione non sarebbe stata sollevata.
ARLabs

1
@ARLabs Immagino che in questo caso sarebbe meglio anche per chi ha risposto poiché se si cattura l'eccezione come mostrato qui, la finestra di dialogo non verrà sicuramente mostrata. Meglio mostrare la finestra di dialogo se puoi e potrebbe sparire se lo stato deve essere ripristinato. Inoltre, mantieni basso l'utilizzo della memoria dell'app in background in modo che non venga distrutto.
androidguy

27

Ciò significa che commit()( show()in caso di DialogFragment) frammento dopo onSaveInstanceState().

Android salverà lo stato del frammento in onSaveInstanceState(). Quindi, se commit()frammenti dopo onSaveInstanceState()frammento, lo stato andrà perso.

Di conseguenza, se l'attività viene interrotta e ricreata in seguito, il frammento non si aggiungerà all'attività, il che è una cattiva esperienza utente. Ecco perché Android non consente la perdita di stato a tutti i costi.

La soluzione più semplice è verificare se lo stato è già stato salvato.

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");
    }
}

Nota: onResumeFragments () chiamerà quando i frammenti riprenderanno.


1
E se volessi mostrare DialogFragment all'interno di un altro frammento?
sviluppatore Android

La nostra soluzione è creare attività e frammentare la classe base e delegare onResumeFragments a frammentare (creiamo onResumeFragments nella classe base del frammento). Non è una bella soluzione ma funziona. Se hai qualche soluzione migliore per favore fatemelo sapere :)
Pongpat

Bene, ho pensato che mostrare la finestra di dialogo in "onStart" dovrebbe funzionare bene, poiché il frammento viene sicuramente mostrato, ma vedo ancora alcuni rapporti di arresto anomalo al riguardo. Mi è stato chiesto di provare a metterlo su "onResume" invece. Riguardo alle alternative, ho visto questo: twigstechtips.blogspot.co.il/2014/01/… , ma è abbastanza strano.
sviluppatore Android

Penso che il motivo per cui twigstechtips.blogspot.co.il/2014/01/… funzioni perché avvia un nuovo thread e quindi tutto il codice del ciclo di vita cioè onStart, onResume, ecc. Chiamato prima che il codice runOnUiThread venga mai eseguito. Ciò significa che lo stato viene già ripristinato prima della chiamata di runOnUiThread.
Pongpat

2
Uso una singola chiamata per postare (eseguibile). Per quanto riguarda getFragmentManager, dipende. Se vuoi condividere quella finestra di dialogo con un'altra attività dovresti usare getFragmentManager, tuttavia, se quella finestra di dialogo esiste solo con fragment getChildFragmentManager sembra una scelta migliore.
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();
}

rif: link


11

Dopo pochi giorni voglio condividere la mia soluzione come l'ho risolta, per mostrare DialogFragment dovresti sovrascrivere il show()metodo e chiamare commitAllowingStateLoss()l' Transactionoggetto. Ecco un esempio in Kotlin:

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

        }

    }

1
In modo che gli sviluppatori non debbano ereditare da DialogFragment si potrebbe cambiare questo per essere una funzione di estensione Kotlin con la firma seguente: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Inoltre, il try-catch non è necessario poiché stai chiamando il commitAllowingStateLoss()metodo e non il commit()metodo.
Adil Hussain il

10

Se la finestra di dialogo non è molto importante (va bene non mostrarla quando l'app è chiusa / non è più visibile), usa:

boolean running = false;

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

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

E apri la tua finestra di dialogo (frammento) solo quando stiamo eseguendo:

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

EDIT, PROBABILMENTE MIGLIORE SOLUZIONE:

Dove onSaveInstanceState viene chiamato nel ciclo di vita è imprevedibile, penso che una soluzione migliore sia controllare isSavedInstanceStateDone () in questo modo:

/**
 * 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;
}

Questo non sembra funzionare, poiché ottengo questa eccezione sulla chiamata al metodo "onStart" (cercando di mostrare il DialogFragment lì).
sviluppatore Android

Mi hai salvato la giornata. Grazie Frank.
Cüneyt

7

Sono anni che mi imbatto in questo problema.
Internet è disseminato di decine (centinaia? Migliaia?) Di discussioni su questo argomento, e la confusione e la disinformazione in esse sembrano abbondanti.
Per peggiorare la situazione, e nello spirito del fumetto xkcd "14 standard", sto aggiungendo la mia risposta sul ring.
xkcd 14 standard

il cancelPendingInputEvents(),commitAllowingStateLoss() , catch (IllegalStateException e), e soluzioni simili tutti sembrano atroce.

Si spera che quanto segue mostri facilmente come riprodurre e risolvere il problema:

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);
    }
};

2
Amo le persone che votano senza spiegazioni. Invece di appena giù di voto, forse sarebbe meglio se spiegano come la mia soluzione è viziata? Posso votare contro il voto negativo di un votante negativo?
swooby

1
Sì, è un problema di SO, scrivo questo problema ogni volta nei suggerimenti, ma non vogliono risolverlo.
CoolMind

2
Penso che i voti negativi possano essere il risultato dell'XKCD incorporato, le risposte non sono davvero il posto per i commenti sociali, (non importa quanto divertenti e / o vere).
RestingRobot

6

prova a utilizzare FragmentTransaction invece di FragmentManager. Penso che il codice seguente risolverà il tuo problema. In caso contrario, per favore fatemelo sapere.

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

MODIFICARE:

Transazione di frammento

Si prega di controllare questo collegamento. Penso che risolverà le tue domande.


4
Qualsiasi spiegazione sul motivo per cui l'utilizzo di FragmentTransaction risolve il problema sarebbe ottimo.
Hemanshu

3
La finestra di dialogo # show (FragmentManager, tag) fa la stessa cosa. Questa non è una soluzione.
William

3
Questa risposta non è la soluzione. DialogFragment # show (ft) e show (fm) fanno esattamente la stessa cosa.
danijoo

@danijoo Hai ragione che entrambi fanno lo stesso lavoro. Ma in pochi telefoni, c'è qualche problema simile a questo se si utilizza fragmentmanager invece di fragmenttransaction. Quindi nel mio caso, questo ha risolto il mio problema.
RIJO RV

6

Utilizzando i nuovi ambiti del ciclo di vita di Activity-KTX è semplice come il seguente esempio di codice:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Questo metodo può essere chiamato direttamente dopo onStop () e mostrerà correttamente la finestra di dialogo una volta che onResume () è stato chiamato al ritorno.


3

Molte visualizzazioni pubblicano eventi di alto livello come gestori di clic nella coda di eventi per l'esecuzione differita. Quindi il problema è che "onSaveInstanceState" è già stato chiamato per l'attività ma la coda degli eventi contiene "evento clic" differito. Quindi, quando questo evento viene inviato al gestore

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

e il tuo codice fa showil IllegalStateException viene lanciato.

La soluzione più semplice è pulire la coda degli eventi, in 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();
        }
}

Hai effettivamente confermato che questo risolve il problema?
mhsmith

Google ha aggiunto questo alla prossima versione delle librerie androidx, attualmente in beta ( activitye fragment).
mhsmith

1
@mhsmith Ricordo che questa soluzione ha risolto il problema nel mio codice con IllegalStateException
sim

2

Rendi globale il tuo oggetto frammento di dialogo e chiama dismissAllowingStateLoss () nel metodo onPause ()

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

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

Non dimenticare di assegnare un valore in fragment e di chiamare show () al clic del pulsante o ovunque.


1

Sebbene non sia menzionato ufficialmente da nessuna parte, ho affrontato questo problema un paio di volte. Nella mia esperienza c'è qualcosa di sbagliato nella libreria di compatibilità che supporta i frammenti su piattaforme precedenti che causa questo problema. Puoi eseguire test utilizzando la normale API di gestione dei frammenti. Se non funziona nulla, puoi utilizzare la finestra di dialogo normale invece del frammento di dialogo.


1
  1. Aggiungi questa classe al tuo progetto: (deve essere nel pacchetto android.support.v4.app )
pacchetto android.support.v4.app;


/ **
 * Creato da Gil il 16/8/2017.
 * /

la classe pubblica StatelessDialogFragment estende DialogFragment {
    / **
     * Visualizzare la finestra di dialogo, aggiungere il frammento utilizzando una transazione esistente e quindi eseguire il commit del file
     * transazione pur consentendo la perdita dello stato.
* * Ti consiglierei di utilizzare {@link #show (FragmentTransaction, String)} la maggior parte delle volte ma * questo è per i dialoghi che davvero non ti interessano. (Debug / Tracciamento / Pubblicità ecc.) * * @param transazione * Una transazione esistente in cui aggiungere il frammento. * @param tag * Il tag per questo frammento, come da * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Restituisce l'identificativo della transazione sottoposta a commit, come da * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (transazione FragmentTransaction, tag String) { mDismissed = false; mShownByMe = true; transazione.add (questo, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Visualizza la finestra di dialogo, aggiungendo il frammento al FragmentManager specificato. Questa è una comodità * per creare esplicitamente una transazione, aggiungendovi il frammento con il tag dato, e * commetterlo senza preoccuparsi dello stato. Ciò non aggiunge la transazione al file * pila posteriore. Quando il frammento viene eliminato, verrà eseguita una nuova transazione per rimuoverlo * dall'attività.
* * Ti consiglierei di utilizzare {@link #show (FragmentManager, String)} la maggior parte delle volte, ma questo è * per i dialoghi che davvero non ti interessano. (Debug / Tracciamento / Pubblicità ecc.) * * * @param manager * Il FragmentManager a cui verrà aggiunto questo frammento. * @param tag * Il tag per questo frammento, come da * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (FragmentManager manager, tag String) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (questo, tag); ft.commitAllowingStateLoss (); } }
  1. Estendi StatelessDialogFragment invece di DialogFragment
  2. Usa il metodo showAllowingStateLoss invece di show

  3. Godere ;)


A cosa servono questi campi booleani? Perché non vengono dichiarati come membri della classe?
undefined

1
I campi booleani sono membri protetti di DialogFragment, i loro nomi ovviamente suggeriscono a cosa servono e dobbiamo aggiornarli per non interferire con la logica di DialogFragment. Nota che nella classe DialogFragment originale, queste funzioni esistono ma senza accesso pubblico
Gil SH

Ough questi membri non sono protetti, sono interni. Stavo ricevendo errori di compilazione mentre StatelessDialogFragmentinserivo uno dei miei pacchetti. Grazie amico. Lo testerò presto in produzione.
undefined

1

usa questo codice

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

invece di

yourFragment.show(fm, "fragment_tag");

1

Ho trovato un'elegante soluzione per questo problema usando la riflessione. Il problema di tutte le soluzioni di cui sopra è che i campi mDismissed e mShownByMe non cambiano il loro stato.

Basta sovrascrivere il metodo "mostra" nel frammento di dialogo del tuo foglio inferiore personalizzato come l'esempio sotto (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()
    }

4
"Ho trovato un'elegante soluzione per questo problema utilizzando la riflessione." com'è elegante?
Mark Buikema

elegante, alla moda, chic, intelligente, simpatico, grazioso
Рома Богдан

1
è l'unica soluzione che ha funzionato per me. Penso che sia elegante
MBH

0

La seguente implementazione può essere utilizzata per risolvere il problema di eseguire in modo sicuro i cambiamenti di stato durante il Activityciclo di vita, in particolare per mostrare le finestre di dialogo: se lo stato dell'istanza è già stato salvato (ad esempio a causa di una modifica della configurazione), le rimanda fino a quando lo stato ripristinato stato eseguito.

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);
    }
}

Quindi usando una classe come questa:

/** 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();
            }
        });
    }
}

Puoi mostrare in sicurezza le finestre di dialogo senza preoccuparti dello stato dell'app:

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();
    }
}

e poi chiama TestDialog.show(this)dal tuo XAppCompatActivity.

Se vuoi creare una classe di dialogo più generica con parametri, puoi salvarli in a Bundlecon gli argomenti nel show()metodo e recuperarli con getArguments()in onCreateDialog().

L'intero approccio potrebbe sembrare un po 'complesso, ma una volta create le due classi base per attività e dialoghi, è abbastanza facile da usare e funziona perfettamente. Può essere utilizzato per altre Fragmentoperazioni basate che potrebbero essere interessate dallo stesso problema.


0

Questo errore sembra verificarsi perché gli eventi di input (come eventi key down o onclick) vengono recapitati dopo la onSaveInstanceStatechiamata.

La soluzione è sovrascrivere la onSaveInstanceStatetua attività e annullare eventuali eventi in sospeso.

@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();
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.