Ao exibir a caixa de diálogo, obtenho "Não é possível executar esta ação após onSaveInstanceState"

121

Alguns usuários estão relatando que, se eles usarem a ação rápida na barra de notificação, estão recebendo um fechamento de força.

Eu mostro uma ação rápida na notificação que chama a classe "TestDialog" . Na classe TestDialog depois de pressionar o botão "soneca", mostrarei o 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");
}

O erro é *IllegalStateException: Can not perform this action after onSaveInstanceState*.

A linha de código onde a IllegarStateException é acionada é:

snoozeDialog.show(fm, "snooze_dialog");

A classe está estendendo "FragmentActivity" e a classe "SnoozeDialog" está estendendo "DialogFragment".

Aqui está o rastreamento completo da pilha do erro:

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)

Não consigo reproduzir esse erro, mas estou recebendo muitos relatórios de erros.

Alguém pode ajudar? Como posso corrigir esse erro?

chrisonline
fonte
2
Você encontrou uma solução? Eu tenho o mesmo problema que você. Eu perguntei aqui: stackoverflow.com/questions/15730878/… Verifique minha pergunta e veja a possível solução que não está funcionando no meu caso. Talvez funcione para você.
rootpanthera
Nenhuma solução ainda :-( E sua sugestão já está adicionado à minha classe.
chrisonline
Verifique a resposta aceita aqui. Isso resolveu meu problema: stackoverflow.com/questions/14177781/…
Bogdan
4
Sua atividade está visível quando esse diálogo é acionado? Parece que isso pode ser causado pelo seu aplicativo tentar exibir uma caixa de diálogo anexada a uma Atividade que foi pausada / parada.
Kai
stackoverflow.com/questions/7575921/… você tentou isso, tenho certeza.
Orion

Respostas:

66

Este é um problema comum . Resolvemos esse problema substituindo show () e manipulando a exceção na classe estendida 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);
        }
    }
}

Observe que a aplicação desse método não alterará os campos internos da classe DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

Isso pode levar a resultados inesperados em alguns casos. Melhor usar commitAllowingStateLoss () em vez de commit ()

Rafael
fonte
3
Mas por que esse problema ocorre? Tudo bem ignorar o erro? O que acontece quando você faz? Afinal, ao clicar, significa que a atividade está ativa e bem ... De qualquer forma, relatei sobre isso aqui, porque considero isso um bug: code.google.com/p/android/issues/detail?id= 207269
desenvolvedor android
1
Você pode estrelar e / ou comentar lá?
desenvolvedor android
2
é melhor chamar super.show (manager, tag) dentro da cláusula try-catch. As bandeiras de propriedade de DialogFragment pode ficar seguro desta forma
Shayan_Aryan
19
Nesse ponto, você pode chamar commitAllowingStateLoss () em vez de commit (). A exceção não seria gerada.
ARLabs 30/01
1
@ARLabs Imagino que, neste caso, seria melhor para o respondente também, pois se você capturar a exceção, como mostrado aqui, a caixa de diálogo definitivamente não será mostrada. Melhor mostrar a caixa de diálogo, se puder, e ela poderá desaparecer se o estado precisar ser restaurado. Além disso, mantenha o uso da memória do aplicativo baixo em segundo plano, para que não seja destruído.
androidguy
27

Isso significa que você commit()( show()no caso de DialogFragment) fragmenta apósonSaveInstanceState() .

O Android salvará seu estado de fragmento em onSaveInstanceState(). Então, se você commit()fragmentar depoisonSaveInstanceState() estado do fragmento, será perdido.

Como resultado, se Activity for morto e recriado mais tarde, o fragmento não será adicionado à atividade, o que é uma experiência ruim do usuário. É por isso que o Android não permite a perda de estado a todo custo.

A solução fácil é verificar se o estado já foi salvo.

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 () chamará quando os fragmentos forem reiniciados.

Pongpat
fonte
1
E se eu quiser mostrar o DialogFragment dentro de outro fragmento?
desenvolvedor android
Nossa solução é criar atividade e classe base de fragmento e delegar onResumeFragments para fragmentar (criamos onResumeFragments na classe base de fragmento). Não é uma solução agradável, mas funciona. Se você tem alguma solução melhor por favor me avise :)
Pongpat
Bem, pensei que exibir a caixa de diálogo no "onStart" deveria funcionar bem, pois o fragmento certamente está sendo mostrado, mas ainda vejo alguns relatórios de falha sobre ele. Fui instruído a tentar colocá-lo no "onResume". Sobre alternativas, vi o seguinte: twigstechtips.blogspot.co.il/2014/01/… , mas é bastante estranho.
desenvolvedor android
Eu acho que o motivo pelo qual twigstechtips.blogspot.co.il/2014/01/… funciona porque ele inicia um novo encadeamento e, portanto, todo o código do ciclo de vida, por exemplo, onStart, onResume etc. chamado antes que o código runOnUiThread seja executado. Isso significa que o estado já restaura antes da execução do runOnUiThread.
Pongpat
2
Eu uso uma chamada única para postar (executável). Em relação ao getFragmentManager, isso depende. Se você deseja compartilhar essa caixa de diálogo com outra atividade, use getFragmentManager; no entanto, se essa caixa de diálogo existir apenas com o fragmento getChildFragmentManager, essa será uma opção melhor.
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: link

huu duy
fonte
11

Depois de alguns dias eu quero compartilhar minha solução como eu fixa-lo, para mostrar DialogFragment você deve substituir show()método dele e chamar commitAllowingStateLoss()no Transactionobjeto. Aqui está um exemplo no Kotlin:

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

        }

    }
Dennis Zinkovski
fonte
1
Para que os desenvolvedores não têm para herdar de DialogFragmentvocê pudesse mudar isso para ser uma função de extensão Kotlin com a seguinte assinatura: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Além disso, o try-catch não é necessário, pois você está chamando o commitAllowingStateLoss()método e não o commit()método.
Adil Hussain
10

Se a caixa de diálogo não for realmente importante (não há problema em não mostrá-la quando o aplicativo foi fechado / não está mais sendo exibido), use:

boolean running = false;

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

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

E abra sua caixa de diálogo (fragmento) somente quando estivermos executando:

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

EDIÇÃO, SOLUÇÃO PROVAVELMENTE MELHOR:

Onde onSaveInstanceState é chamado no ciclo de vida é imprevisível, acho que uma solução melhor é verificar isSavedInstanceStateDone () assim:

/**
 * 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;
}
Frank
fonte
Isso parece não funcionar, pois recebo essa exceção na chamada do método "onStart" (tentando mostrar o DialogFragment lá).
desenvolvedor android
Você salvou meu dia. Obrigado Frank.
Cüneyt 29/02
6

tente usar FragmentTransaction em vez de FragmentManager. Acho que o código abaixo resolverá seu problema. Se não, por favor me avise.

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

EDITAR:

Transação de fragmento

Por favor, verifique este link. Eu acho que isso resolverá suas dúvidas.

RIJO RV
fonte
4
Qualquer explicação sobre por que o uso do FragmentTransaction corrige o problema seria ótimo.
Hemanshu
3
A caixa de diálogo # show (FragmentManager, tag) faz a mesma coisa. Esta não é uma solução.
William
3
Esta resposta não é a solução. DialogFragment # show (ft) e show (fm) fazem exatamente a mesma coisa.
danijoo
@danijoo Você está certo que isso faz o mesmo trabalho. Mas em alguns telefones, há algum problema semelhante a esse se você estiver usando o gerenciador de fragmentos, em vez da transação de fragmentação. Então, no meu caso, isso resolveu meu problema.
RIJO RV 02/02
6

Corri para esse problema há anos.
Os Internets estão repletos de dezenas (centenas? Milhares?) De discussões sobre isso, e confusão e desinformação nelas parecem abundantes.
Para piorar a situação, e no espírito dos quadrinhos "14 standards" do xkcd, estou jogando minha resposta no ringue.
xkcd 14 standards

A cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), e soluções semelhantes todos parecem atroz.

Felizmente, o seguinte mostra facilmente como reproduzir e corrigir o 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);
    }
};
swooby
fonte
1
Eu amo pessoas que votam sem explicação. Em vez de apenas votar, talvez seria melhor se eles explicassem como minha solução é falha? Posso votar negativamente no voto negativo de um eleitor?
swooby
1
Sim, é um problema de SO, escrevo esse problema sempre em sugestões, mas elas não querem resolver.
CoolMind 01/02/19
2
Acho que votos negativos podem ser o resultado do XKCD incorporado, as respostas realmente não são o lugar para comentários sociais (por mais engraçados e / ou verdadeiros).
precisa saber é o seguinte
5

O uso dos novos escopos de ciclo de vida do Activity-KTX é tão simples quanto o seguinte exemplo de código:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Este método pode ser chamado diretamente após onStop () e mostrará com êxito a caixa de diálogo assim que onResume () for chamado ao retornar.

PenguinDan
fonte
3

Muitas visualizações postam eventos de alto nível, como manipuladores de cliques na fila de eventos para executar adiados. Portanto, o problema é que "onSaveInstanceState" já foi chamado para a Atividade, mas a fila de eventos contém "evento de clique" adiado. Portanto, quando este evento é despachado para seu manipulador

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 seu código faz showo IllegalStateException é lançado.

A solução mais simples é limpar a fila de eventos, em 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
fonte
Você realmente confirmou que isso resolve o problema?
mhsmith
O Google adicionou isso à próxima versão das bibliotecas androidx, atualmente na versão beta ( activitye fragment).
mhsmith
1
@mhsmith Lembro-me de que esta solução resolveu o problema no meu código com IllegalStateException
sim
2

Torne seu objeto de fragmento de diálogo global e chame o método allowAllowingStateLoss () no método onPause ()

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

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

Não se esqueça de atribuir valor no fragmento e chamar show () ao clicar no botão ou em qualquer lugar.

Rohit Rajpal
fonte
1

Embora não seja oficialmente mencionado em nenhum lugar, mas eu enfrentei esse problema algumas vezes. Na minha experiência, há algo errado na biblioteca de compatibilidade que suporta fragmentos em plataformas mais antigas, o que causa esse problema. Você pode testar isso usando a API normal do gerenciador de fragmentos. Se nada funcionar, você poderá usar a caixa de diálogo normal em vez do fragmento da caixa de diálogo.

Dalvinder Singh
fonte
1
  1. Adicione esta classe ao seu projeto: (deve estar no pacote android.support.v4.app )
pacote android.support.v4.app;


/ **
 * Criado por Gil em 16/08/2017.
 * /

classe pública StatelessDialogFragment estende DialogFragment {
    / **
     * Exiba a caixa de diálogo, adicionando o fragmento usando uma transação existente e confirmando o
     * transação, permitindo perda de estado.
* * Eu recomendo que você use {@link #show (FragmentTransaction, String)} na maioria das vezes, mas * isto é para diálogos com os quais você realmente não se importa. (Depuração / rastreamento / anúncios etc.) * * transação @param * Uma transação existente na qual adicionar o fragmento. * tag @param * A tag desse fragmento, conforme * {@link FragmentTransaction # add (Fragmento, String) FragmentTransaction.add}. * @return Retorna o identificador da transação confirmada, conforme * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (transação FragmentTransaction, tag String) { mDismissed = false; mShownByMe = true; transaction.add (essa tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Exiba a caixa de diálogo, adicionando o fragmento ao FragmentManager fornecido. Esta é uma conveniência * para criar explicitamente uma transação, adicionar o fragmento a ela com a tag fornecida e * cometê-lo sem se preocupar com o estado. Isso não adiciona a transação ao * pilha traseira. Quando o fragmento é descartado, uma nova transação será executada para removê-lo * da atividade.
* * Eu recomendo que você use {@link #show (FragmentManager, String)} na maioria das vezes, mas isso é * para diálogos com os quais você realmente não se importa. (Depuração / rastreamento / anúncios etc.) * * * @param manager * O FragmentManager ao qual este fragmento será adicionado. * tag @param * A tag desse fragmento, conforme * {@link FragmentTransaction # add (Fragmento, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (gerenciador de FragmentManager, tag String) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (isso, tag); ft.commitAllowingStateLoss (); } }
  1. Estender StatelessDialogFragment em vez de DialogFragment
  2. Use o método showAllowingStateLoss em vez de show

  3. Aproveitar ;)

Gil SH
fonte
Para que servem todos esses campos booleanos? Por que não são declarados como membros da classe?
undefined
1
Os campos booleanos são membros protegidos do DialogFragment, seus nomes obviamente sugerem para que servem e precisamos atualizá-los para não interferir na lógica do DialogFragment. Observe que na classe DialogFragment original, essas funções existem mas sem acesso público
Gil SH
Se você está StatelessDialogFragmentprocurando por uma solução para o seu problema, entre em contato com a nossa equipe de suporte para obter mais informações sobre os nossos serviços.
Indefinido
1

use este código

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

ao invés de

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

Encontrei uma solução elegante para esse problema usando a reflexão. O problema de todas as soluções acima é que os campos mDismissed e mShownByMe não alteram seu estado.

Basta substituir o método "show" em seu próprio fragmento de diálogo da folha de baixo personalizado, como a amostra abaixo (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()
    }
Рома Богдан
fonte
3
"Encontrei uma solução elegante para esse problema usando a reflexão." como é elegante?
Mark Buikema
elegante, elegante, chique, inteligente, agradável, gracioso
Рома Богдан
1
é a única solução que funcionou para mim. Eu acho que é elegante
MBH
0

A implementação a seguir pode ser usada para resolver o problema de realizar alterações de estado com segurança durante o Activityciclo de vida, principalmente para mostrar diálogos: se o estado da instância já tiver sido salvo (por exemplo, devido a uma alteração na configuração), ele será adiado até que o estado retomado realizado.

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

Então, usando uma classe como esta:

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

Você pode mostrar diálogos com segurança sem se preocupar com o estado do aplicativo:

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 depois ligue TestDialog.show(this)de dentro do seu XAppCompatActivity.

Se você deseja criar uma classe de diálogo mais genérica com parâmetros, você pode salvá-los em um Bundlecom os argumentos do show()método e recuperá-los getArguments()em onCreateDialog().

Toda a abordagem pode parecer um pouco complexa, mas depois de criar as duas classes base para atividades e diálogos, é bastante fácil de usar e funciona perfeitamente. Pode ser usado para outras Fragmentoperações baseadas que podem ser afetadas pelo mesmo problema.

gicci
fonte
0

Este erro parece estar ocorrendo porque os eventos de entrada (como eventos de pressionamento de tecla ou onclick) estão sendo entregues após a onSaveInstanceStatechamada.

A solução é substituir onSaveInstanceStatesua Atividade e cancelar quaisquer eventos pendentes.

@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
fonte