IllegalStateException: Não é possível executar esta ação após onSaveInstanceState com ViewPager

496

Estou recebendo relatórios de usuários do meu aplicativo no mercado, fornecendo a seguinte exceção:

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)

Aparentemente, tem algo a ver com um FragmentManager, que não uso. O stacktrace não mostra nenhuma das minhas próprias classes, portanto, não tenho idéia de onde essa exceção ocorre e como evitá-la.

Para o registro: Eu tenho um tabhost e em cada guia existe um ActivityGroup alternando entre Atividades.

nhaarman
fonte
2
Eu encontrei esta pergunta discutir a mesma questão, mas nenhuma solução não quer .. stackoverflow.com/questions/7469082/...
nhaarman
3
Enquanto você não estiver usando FragmentManager, o Honeycomb certamente está. Isso está acontecendo em tablets Honeycomb reais? Ou será que alguém está executando um Honeycomb hackeado em um telefone ou algo assim e é essa edição hackeada que está tendo dificuldades?
CommonsWare
1
Eu não faço ideia. Esta é a única informação que eu entrar no mercado de console do desenvolvedor, a mensagem do usuário não contém informações úteis quer ..
nhaarman
Estou usando o Flurry, que mostra 11 sessões com o Android 3.0.1 e tenho 11 relatórios dessa exceção. Pode ser coincidência. O Android 3.1 e 3.2 têm 56 e 38 sessões, respectivamente.
Nhaarman
O relatório de erros do Market possui uma seção 'Plataforma', às vezes possui a versão Android do dispositivo.
Nikolay Elenkov

Respostas:

720

Por favor, verifique minha resposta aqui . Basicamente, eu apenas tinha que:

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

Não faça a chamada super()no saveInstanceStatemétodo. Isso estava atrapalhando as coisas ...

Este é um bug conhecido no pacote de suporte.

Se você precisar salvar a instância e adicionar algo ao seu, outState Bundlepoderá usar o seguinte:

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

No final, a solução adequada foi (como visto nos comentários) usar:

transaction.commitAllowingStateLoss();

ao adicionar ou executar o FragmentTransactionque estava causando o Exception.

Ovidiu Latcu
fonte
370
Você deve usar commitAllowingStateLoss () em vez de commit ()
meh
18
Este comentário sobre commitAllowingStateLoss () é uma resposta por si só - você deve publicá-lo dessa forma.
Risadinha 15/10
20
Com relação a 'commitAllowingStateLoss' - /> "Isso é perigoso porque a confirmação pode ser perdida se a atividade precisar ser restaurada posteriormente de seu estado, portanto, isso deve ser usado apenas nos casos em que o estado da interface do usuário for aceitável mudar inesperadamente. o usuário."
Codeversed
10
Se eu procurar na fonte v4, popBackStackImmediateela falhará imediatamente se o estado tiver sido salvo. A adição anterior do fragmento com commitAllowingStateLossnão desempenha nenhum papel. Meus testes mostram que isso é verdade. Não tem efeito sobre essa exceção específica. O que precisamos é de um popBackStackImmediateAllowingStateLossmétodo.
Synesso
3
@ DanieleB sim, eu postei uma resposta aqui. Mas encontrei uma solução ainda melhor usando o barramento de mensagens Otto: registre o fragmento como assinante e ouça o resultado assíncrono do barramento. Cancele o registro na pausa e registre-se novamente no currículo. O async também precisa de um método Produce para os horários em que é concluído e o fragmento está em pausa. Quando tiver tempo, atualizarei minha resposta com mais detalhes.
Synesso 8/03/2015
130

Existem muitos problemas relacionados com uma mensagem de erro semelhante. Verifique a segunda linha desse rastreamento de pilha específico. Esta exceção está especificamente relacionada à chamada para FragmentManagerImpl.popBackStackImmediate.

Essa chamada de método, como popBackStack, sempre falhará IllegalStateExceptionse o estado da sessão já tiver sido salvo. Verifique a fonte. Não há nada que você possa fazer para impedir que essa exceção seja lançada.

  • Remover a chamada para super.onSaveInstanceStatenão ajudará.
  • Criar o fragmento com commitAllowingStateLossnão ajudará.

Aqui está como eu observei o problema:

  • Há um formulário com um botão de envio.
  • Quando o botão é clicado, uma caixa de diálogo é criada e um processo assíncrono é iniciado.
  • O usuário clica na chave inicial antes que o processo seja concluído - onSaveInstanceStateé chamado.
  • O processo é concluído, um retorno de chamada é feito e popBackStackImmediateé tentada.
  • IllegalStateException é jogado.

Aqui está o que eu fiz para resolvê-lo:

Como não é possível evitar IllegalStateExceptiono retorno de chamada, pegue e ignore.

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

Isso é suficiente para impedir o aplicativo de travar. Mas agora o usuário restaurará o aplicativo e verá que o botão que eles pensavam ter pressionado não havia sido pressionado (eles acham). O fragmento do formulário ainda está sendo exibido!

Para corrigir isso, quando o diálogo for criado, indique algum estado para indicar que o processo foi iniciado.

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

E salve esse estado no pacote.

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

Não se esqueça de carregá-lo novamente em onViewCreated

Em seguida, ao retomar, reverta os fragmentos se o envio foi tentado anteriormente. Isso impede que o usuário volte ao que parece ser um formulário não enviado.

@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
fonte
5
Leitura interessante sobre isso aqui: androiddesignpatterns.com/2013/08/…
Pascal
Se você usa o DialogFragment, eu fiz uma alternativa a ele aqui: github.com/AndroidDeveloperLB/DialogShard
desenvolvedor Android
E se o popBackStackImmediatefoi chamado pelo próprio Android?
Kimi Chiu
1
Absolutamente ótimo. Essa deve ser a resposta aceita. Muito obrigado! Talvez eu adicionaria submitPressed = false; após popBackStackInmediate.
Neonigma 15/05/19
Eu não usei o método public void onSaveInstanceState (Bundle outState). Preciso definir o método vazio para public void onSaveInstanceState (Bundle outState)?
Faxriddin Abdullayev 23/01
55

Verifique se a atividade isFinishing()antes de mostrar o fragmento e preste atenção commitAllowingStateLoss().

Exemplo:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Naskov
fonte
1
! isFinishing () &&! isDestroyed () não funciona para mim.
Allen Vork 6/18
! isFinishing () &&! isDestroyed () funcionou para mim, mas requer a API 17. Mas simplesmente não mostra a DialogFragment. Consulte stackoverflow.com/questions/15729138/… para outras boas soluções, stackoverflow.com/a/41813953/2914140 me ajudou.
CoolMind 01/02/19
29

É outubro de 2017 e o Google cria a Biblioteca de suporte do Android com o novo componente chamado Ciclo de vida. Ele fornece uma nova idéia para o problema 'Não é possível executar esta ação após onSaveInstanceState'.

Em resumo:

  • Use o componente do ciclo de vida para determinar se é hora correta de abrir seu fragmento.

Versão mais longa com explicação:

  • por que esse problema aparece?

    É porque você está tentando usar a FragmentManagerpartir de sua atividade (o que sustentará seu fragmento, suponho?) Para confirmar uma transação para você. Geralmente, parece que você está tentando fazer alguma transação para um fragmento que está por vir, enquanto a atividade do host já chama o savedInstanceStatemétodo (o usuário pode tocar no botão de início para que a atividade chame onStop(), no meu caso, é o motivo)

    Normalmente, esse problema não deve ocorrer - sempre tentamos carregar fragmentos em atividade desde o início, como se o onCreate()método fosse o local perfeito para isso. Mas às vezes isso acontece , especialmente quando você não pode decidir qual fragmento será carregado para essa atividade ou está tentando carregar fragmento de um AsyncTaskbloco (ou qualquer coisa levará um tempo). O tempo antes da transação do fragmento realmente acontecer, mas após o onCreate()método da atividade , o usuário pode fazer qualquer coisa. Se o usuário pressionar o botão home, que aciona o onSavedInstanceState()método da atividade , can not perform this actionocorrerá uma falha.

    Se alguém quiser ver mais detalhes desta edição, sugiro que dêem uma olhada nesta postagem do blog . Parece profundamente dentro da camada de código-fonte e explica muito sobre isso. Além disso, explica por que você não deve usar o commitAllowingStateLoss()método para solucionar esse problema (confie em mim, ele não oferece nada de bom para o seu código)

  • Como consertar isto?

    • Devo usar o commitAllowingStateLoss()método para carregar fragmento? Não, você não deveria ;

    • Devo substituir o onSaveInstanceStatemétodo, ignorar o supermétodo dentro dele? Não, você não deveria ;

    • Devo usar a isFinishingatividade interna mágica , para verificar se a atividade do host está no momento certo para a transação do fragmento? Sim, isso parece o caminho certo a fazer.

  • Veja o que o componente Lifecycle pode fazer.

    Basicamente, o Google faz alguma implementação dentro da AppCompatActivityclasse (e várias outras classes base que você deve usar em seu projeto), o que facilita a determinação do estado atual do ciclo de vida . Dê uma olhada no nosso problema: por que esse problema aconteceria? É porque fazemos algo no momento errado. Portanto, tentamos não fazê-lo, e esse problema desaparecerá.

    Eu codigo um pouco para o meu próprio projeto, aqui está o que eu faço usando LifeCycle. Eu codigo em 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()
    }

Como mostro acima. Vou verificar o estado do ciclo de vida da atividade do host. Com o componente Lifecycle na biblioteca de suporte, isso pode ser mais específico. O código lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)significa que, se o estado atual for pelo menos onResume, o mais tardar? O que garante que meu método não seja executado durante algum outro estado de vida (como onStop).

  • Está tudo pronto?

    Claro que não. O código que mostrei mostra uma nova maneira de impedir a falha do aplicativo. Mas se for para o estado de onStop, essa linha de código não fará as coisas e, portanto, não mostrará nada na tela. Quando os usuários retornam ao aplicativo, eles verão uma tela vazia, que é a atividade do host vazio, sem mostrar nenhum fragmento. É uma experiência ruim (sim, um pouco melhor que uma batida).

    Então, aqui eu gostaria que houvesse algo mais agradável: o aplicativo não falhará se chegar ao estado de vida posterior onResume, o método de transação é sensível ao estado de vida; além disso, a atividade tentará continuar a concluir a ação da transação de fragmento depois que o usuário voltar ao nosso aplicativo.

    Eu adiciono algo mais a este método:

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

Eu mantenho uma lista dentro desta dispatcherclasse, para armazenar esses fragmentos não tem chance de concluir a ação da transação. E quando o usuário volta da tela inicial e descobre que ainda existe um fragmento aguardando o lançamento, ele irá para o resume()método sob a @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)anotação. Agora acho que deveria estar funcionando como eu esperava.

Anthonyeef
fonte
8
Seria bom usar Java em vez de Kotlin
Shchvova
1
Por que sua implementação FragmentDispatcherusa uma lista para armazenar fragmentos pendentes se houver apenas um fragmento restaurado?
fraherm
21

Aqui está uma solução diferente para esse problema.

Usando uma variável de membro particular, você pode definir os dados retornados como uma intenção que pode ser processada após super.onResume ();

Igual a:

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
fonte
7
Dependendo da ação que você estava executando e que não era permitida, talvez seja necessário passar para um momento ainda mais tarde que onResume (). Para insatcne, se FragmentTransaction.commit () for o problema, ele precisará acessar onPostResume ().
Pjv
1
Esta é a resposta para esta pergunta para mim. Como eu precisava encaminhar uma tag NFC recebida para a atividade anterior, foi isso que fez por mim.
Janis Peisenieks
7
Para mim, estava acontecendo porque eu não estava ligando super.onActivityResult().
Sufian
20

Solução curta e funcional:

Siga etapas simples

Passos

Etapa 1: Substitua o onSaveInstanceStateestado no respectivo fragmento. E remova super método dele.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Etapa 2: usar fragmentTransaction.commitAllowingStateLoss( );

em vez de fragmentTransaction.commit( ); operações de fragmento while.

Vinayak
fonte
Resposta não é copiado ou forma arbitrada mais onde .É foi enviada para ajuda às pessoas de minha solução de trabalho que tenho por vários tentativa e erro
Vinayak
12

CUIDADO , o uso transaction.commitAllowingStateLoss()pode resultar em uma experiência ruim para o usuário. Para obter mais informações sobre o motivo dessa exceção, consulte esta postagem .

Eric Brandwein
fonte
6
Esta não fornece resposta para a pergunta, você deve fornecer uma resposta válida para a questão
Umar Ata
10

Encontrei uma solução suja para esse tipo de problema. Se você ainda deseja mantê-lo ActivityGroupspor qualquer motivo (eu tinha motivos de limitação de tempo), basta implementar

public void onBackPressed() {}

no seu Activitye faça algum backcódigo lá. mesmo que não exista esse método em dispositivos mais antigos, esse método será chamado pelos mais novos.

sabrerider
fonte
6

Não use commitAllowingStateLoss (), ele deve ser usado apenas nos casos em que não há problema em o estado da interface do usuário mudar inesperadamente no usuário.

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

Se a transação ocorrer no ChildFragmentManager de parentFragment, use parentFragment.isResume () fora para verificar.

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

Eu tive um problema semelhante, o cenário era o seguinte:

  • Minha atividade está adicionando / substituindo fragmentos de lista.
  • Cada fragmento da lista tem uma referência à atividade, para notificá-la quando um item da lista é clicado (padrão do observador).
  • Cada fragmento de lista chama setRetainInstance (true); no seu método onCreate .

O método onCreate da atividade foi assim:

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

A exceção foi lançada porque, quando a configuração é alterada (o dispositivo gira), a atividade é criada, o fragmento principal é recuperado do histórico do gerenciador de fragmentos e, ao mesmo tempo, o fragmento já possui uma referência OLD à atividade destruída.

alterar a implementação para isso resolveu o problema:

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

você precisa definir seus ouvintes toda vez que a atividade é criada para evitar a situação em que os fragmentos têm referências a instâncias destruídas antigas da atividade.

Mina Samy
fonte
5

Se você herdar de FragmentActivity, deverá chamar a superclasse em onActivityResult():

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

Se você não fizer isso e tentar mostrar uma caixa de diálogo de fragmento nesse método, poderá obter OPs IllegalStateException. (Para ser sincero, não entendo bem por que a chamada super corrige o problema. Já onActivityResult()foi chamada antes onResume(), portanto ainda não deve ser permitido mostrar uma caixa de diálogo de fragmento.)

Lawrence Kesteloot
fonte
1
Gostaria de saber por que isso resolve o problema.
Big McLargeHuge
3

Eu estava recebendo essa exceção quando pressionava o botão Voltar para cancelar o seletor de intenção na atividade do fragmento do mapa. Eu resolvi isso substituindo o código de onResume (onde eu estava inicializando o fragmento) para onstart () e o aplicativo está funcionando bem.

DCS
fonte
2

Eu acho que usar transaction.commitAllowingStateLoss();não é a melhor solução. Essa exceção será lançada quando a configuração da atividade for alterada e o fragmento onSavedInstanceState()for chamado e, posteriormente, o método de retorno de chamada assíncrona tentar confirmar o fragmento.

A solução simples pode ser verificar se a atividade está mudando a configuração ou não

por exemplo, cheque isChangingConfigurations()

ie

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

Confira este link também

Amol Desai
fonte
De alguma forma, recebi essa exceção quando o usuário clica em algo (o clique é o gatilho para executar a transação). Como isso poderia ser? Sua solução aqui aqui?
desenvolvedor android
@androiddeveloper o que mais você está fazendo no clique do usuário. de alguma forma fragmento está salvando seu estado antes de confirmar a transação
Amol Desai
A exceção foi lançada na linha exata de confirmação da transação. Além disso, tive um erro de digitação estranho: em vez de "aqui aqui", eu quis dizer "trabalhar aqui".
desenvolvedor android
@androiddeveloper você está certo! mas antes de confirmar a transação, você está gerando algum thread em segundo plano ou algo assim?
Amol Desai
Eu acho que não (desculpe, estou fora do escritório), mas por que isso importa? Aqui estão todas as coisas da interface do usuário ... Se eu criar algo em um encadeamento em segundo plano, haveria exceções lá, além de não colocar coisas relacionadas à interface do usuário em encadeamentos em segundo plano, pois é muito arriscado.
desenvolvedor android
2

Possivelmente, a solução mais fácil e simples que encontrei no meu caso foi evitar estourar o fragmento ofensivo da pilha em resposta ao resultado da atividade. Então, alterando esta chamada no meu onActivityResult():

popMyFragmentAndMoveOn();

para isso:

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

ajudou no meu caso.

mojuba
fonte
2

Se você estiver executando alguma FragmentTransaction no onActivityResult, o que você pode fazer, poderá definir algum valor booleano no onActivityResult e, no onResume, você poderá executar o FragmentTransaction com base no valor booleano. Por favor, consulte o código abaixo.

@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
fonte
Não publique o código como imagem, use a formatação do código.
Micer
1
Sim, isso me ajudou .., Esta é a solução exata e adequada para o dispositivo marshmallow. Eu recebi essa exceção e resolvi com esse truque simples .. !! Daí votou.
sandhya sasane 16/09
2

Cortesia: Solução para IllegalStateException

Esse problema me incomodou por muito tempo, mas felizmente eu vim com uma solução concreta para ele. Uma explicação detalhada disso está aqui .

Usar commitAllowStateloss () pode impedir essa exceção, mas levaria a irregularidades da interface do usuário. Até agora, entendemos que IllegalStateException é encontrada quando tentamos confirmar um fragmento após a perda do estado da atividade - portanto, devemos atrasar a transação até que o estado seja restaurado. .Pode ser feito simplesmente assim

Declarar duas variáveis ​​booleanas privadas

 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;

Agora, em onPostResume () e onPause, definimos e desmarcamos nossa variável booleana isTransactionSafe. A idéia é marcar a transação segura apenas quando a atividade estiver em primeiro plano, para que não haja chance de perda de status.

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

-O que fizemos até agora salvará o IllegalStateException, mas nossas transações serão perdidas se forem feitas após a atividade ser movida para segundo plano, como commitAllowStateloss (). Para ajudar, temos a variável booleana 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
fonte
2

Transações de fragmentos não devem ser executadas depois Activity.onStop()! Verifique se você não possui retornos de chamada que possam executar a transação depois onStop(). É melhor corrigir o motivo, em vez de tentar contornar o problema com abordagens como.commitAllowingStateLoss()

Ivo Stoyanov
fonte
1

A partir da biblioteca de suporte versão 24.0.0, é possível chamar o FragmentTransaction.commitNow()método que confirma esta transação de forma síncrona, em vez de chamar commit()seguido por executePendingTransactions(). Como a documentação diz essa abordagem ainda melhor:

É preferível chamar commitNow do que chamar commit () seguido de executePendingTransactions (), pois o último terá o efeito colateral de tentar confirmar todas as transações pendentes atualmente, seja esse o comportamento desejado ou não.

Volodymyr
fonte
1

Sempre que você estiver tentando carregar um fragmento em sua atividade, verifique se a atividade está em retomada e não em estado de pausa. No estado de pausa, você pode acabar perdendo a operação de confirmação concluída.

Você pode usar transaction.commitAllowingStateLoss () em vez de transaction.commit () para carregar fragmento

ou

Crie um booleano e verifique se a atividade não será pausada

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

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

enquanto carrega a verificação de fragmentos

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

Para contornar esse problema, podemos usar o Componente de Arquitetura de Navegação , que foi introduzido no Google I / O 2018. O Componente de Arquitetura de Navegação simplifica a implementação da navegação em um aplicativo Android.

Levon Petrosyan
fonte
Não é bom o suficiente, e bugy (manipulação pobre deeplink, não pode salvar o estado mostrar / ocultar fragmentos e alguma questão importante que ainda está em aberto)
DO01
1

Em relação à ótima resposta para @Anthonyeef, aqui está um código de exemplo em 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
fonte
1

Se você tiver travado com o método popBackStack () ou popBackStackImmediate (), tente corrigir com:

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

Isso também funcionou para mim.

Tapa Save
fonte
Observe que ele requer API 26 e superior #
Itay Feldman
1

No meu caso, recebi esse erro em um método de substituição chamado onActivityResult. Depois de cavar, descobri que talvez eu precisasse chamar ' super ' antes.
Eu adicionei e funcionou

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

}

Talvez você só precise adicionar um 'super' em qualquer substituição que estiver fazendo antes do seu código.

Gian Gomen
fonte
1

Extensão 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()
}

Uso:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
CoolMind
fonte
Você pode remover o () -> antes do Fragment
Claire
@ Claire, obrigado! Você quer dizer mudar para fragment: Fragment? Sim, tentei essa variante, mas nesse caso um fragmento seria criado em todos os casos (mesmo quando fragmentManager == null, mas não encontrei essa situação). Atualizei a resposta e alterei nulo para marcar addToBackStack().
CoolMind 5/03
1

Essa falha ocorre devido a uma FragmentTransaction ser confirmada após o ciclo de vida de sua propriedade já ter sido executado em SaveInstanceState. Isso geralmente é causado pela confirmação de FragmentTransactions de um retorno de chamada assíncrono. Confira o recurso vinculado para obter mais detalhes.

Transação de fragmentos e perda de estado da atividade

http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

Bholendra Singh
fonte
0

Adicione isso em sua atividade

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

Eu também experimentei esse problema e o problema ocorre toda vez que o contexto FragmentActivityé alterado (por exemplo, a orientação da tela é alterada etc.). Portanto, a melhor solução é atualizar o contexto a partir do seu FragmentActivity.

Adão
fonte
0

Acabei criando um fragmento base e fiz com que todos os fragmentos do meu aplicativo o estendessem

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

Então, quando tento mostrar um fragmento, uso em showAllowingStateLossvez deshow

como isso:

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

Eu vim para esta solução a partir deste PR: https://github.com/googlesamples/easypermissions/pull/170/files

Ahmad El-Melegy
fonte
0

Outra solução possível, que não tenho certeza se ajuda em todos os casos (origem aqui ):

@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();
        }
    }
}
desenvolvedor android
fonte
0

Eu sei que há uma resposta aceita por @Ovidiu Latcu, mas depois de um tempo, o erro ainda persiste.

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

Crashlytics ainda está me enviando essa mensagem de erro estranha.

No entanto, agora o erro ocorre apenas na versão 7+ (Nougat). Minha correção foi usar commitAllowingStateLoss () em vez de commit () no fragmentTransaction.

Esta publicação é útil para commitAllowingStateLoss () e nunca mais teve um problema de fragmento.

Para resumir, a resposta aceita aqui pode funcionar em versões anteriores ao Nougat para Android.

Isso pode poupar alguém algumas horas de pesquisa. codificações felizes. <3 aplausos

ralphgabb
fonte