IllegalStateException: no se puede realizar esta acción después de onSaveInstanceState con ViewPager

496

Recibo informes de usuarios de mi aplicación en el mercado, entregando la siguiente excepción:

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 tiene algo que ver con un FragmentManager, que no uso. El stacktrace no muestra ninguna de mis propias clases, por lo que no tengo idea de dónde ocurre esta excepción y cómo prevenirla.

Para el registro: tengo un tabhost, y en cada pestaña hay un ActivityGroup que cambia entre Actividades.

nhaarman
fuente
2
Encontré esta pregunta discutiendo el mismo problema, pero tampoco hay solución ... stackoverflow.com/questions/7469082/…
nhaarman
3
Mientras no esté usando FragmentManager, Honeycomb ciertamente lo está. ¿Está sucediendo esto en tabletas Honeycomb reales? ¿O podría ser que alguien está ejecutando un Honeycomb pirateado en un teléfono o algo así y es esa edición pirateada la que tiene dificultades?
CommonsWare
1
No tengo idea. Esta es la única información que me meto en la consola del mercado, el mensaje de usuario no contiene ninguna información útil, ya sea ..
nhaarman
Estoy usando Flurry, que me muestra 11 sesiones con Android 3.0.1, y tengo 11 informes de esta excepción. Sin embargo, podría ser una coincidencia. Android 3.1 y 3.2 tienen 56 y 38 sesiones, respectivamente.
nhaarman
El informe de error de Market tiene una sección 'Plataforma', a veces tiene la versión de Android del dispositivo.
Nikolay Elenkov

Respuestas:

720

Por favor revisa mi respuesta aquí . Básicamente solo tenía que:

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

No haga la llamada a super()la saveInstanceStatemétodo. Esto estaba arruinando las cosas ...

Este es un error conocido en el paquete de soporte.

Si necesita guardar la instancia y agregarle algo outState Bundle, puede usar lo siguiente:

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

Al final, la solución adecuada fue (como se ve en los comentarios) usar:

transaction.commitAllowingStateLoss();

al agregar o realizar lo FragmentTransactionque estaba causando el Exception.

Ovidiu Latcu
fuente
370
Debería usar commitAllowingStateLoss () en lugar de commit ()
meh
18
Este comentario sobre commitAllowingStateLoss () es una respuesta por derecho propio; debe publicarlo así.
Risadinha
20
Con respecto a 'commitAllowingStateLoss' - /> "Esto es peligroso porque la confirmación puede perderse si la actividad necesita ser restaurada más tarde de su estado, por lo que esto solo debe usarse para casos en los que está bien que el estado de la IU cambie inesperadamente en el usuario."
Codeversed
10
Si miro la fuente v4 popBackStackImmediate, inmediatamente falla si el estado se ha guardado. Anteriormente agregar el fragmento con commitAllowingStateLossno juega ningún papel. Mi prueba muestra que esto es cierto. No tiene ningún efecto sobre esta excepción específica. Lo que necesitamos es un popBackStackImmediateAllowingStateLossmétodo.
Synesso
3
@DanieleB sí, he publicado una respuesta aquí. Pero en realidad he encontrado una solución aún mejor utilizando el bus de mensajes Otto: registre el fragmento como suscriptor y escuche el resultado asíncrono del bus. Anule el registro en pausa y vuelva a registrarse en el currículum. El asíncrono también necesita un método de producción para los momentos en que se completa y el fragmento está en pausa. Cuando tenga tiempo, actualizaré mi respuesta con esto con más detalle.
Synesso
130

Hay muchos problemas relacionados con un mensaje de error similar. Verifique la segunda línea de este seguimiento de pila en particular. Esta excepción está específicamente relacionada con la llamada a FragmentManagerImpl.popBackStackImmediate.

Esta llamada al método, como popBackStack, siempre fallará IllegalStateExceptionsi el estado de la sesión ya se ha guardado. Verifica la fuente. No hay nada que pueda hacer para evitar que se lance esta excepción.

  • Eliminar la llamada a super.onSaveInstanceStateno ayudará.
  • Crear el Fragmento con commitAllowingStateLossno ayudará.

Así es como observé el problema:

  • Hay un formulario con un botón de enviar.
  • Cuando se hace clic en el botón, se crea un diálogo y se inicia un proceso asíncrono.
  • Se onSaveInstanceStatellama al usuario que hace clic en la tecla de inicio antes de que finalice el proceso .
  • El proceso se completa, se realiza una devolución de llamada y popBackStackImmediatese intenta.
  • IllegalStateException es aventado.

Esto es lo que hice para resolverlo:

Como no es posible evitar IllegalStateExceptionla devolución de llamada, cógela e ignórala.

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

Esto es suficiente para evitar que la aplicación se bloquee. Pero ahora el usuario restaurará la aplicación y verá que el botón que creyeron haber presionado no se ha presionado en absoluto (piensan). ¡El fragmento de formulario todavía se muestra!

Para solucionar esto, cuando se crea el cuadro de diálogo, haga un estado para indicar que el proceso ha comenzado.

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

Y guarde este estado en el paquete.

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

No olvides cargarlo nuevamente en onViewCreated

Luego, al reanudar, revierta los fragmentos si se intentó enviar previamente. Esto evita que el usuario regrese a lo que parece un formulario no 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
fuente
55
Lectura interesante sobre eso aquí: androiddesignpatterns.com/2013/08/…
Pascal
Si usa DialogFragment, he hecho una alternativa aquí: github.com/AndroidDeveloperLB/DialogShard
desarrollador de Android
¿Qué pasa si popBackStackImmediatefue llamado por el propio Android?
Kimi Chiu el
1
Absolutamente genial. Esta debería ser la respuesta aceptada. ¡Muchas gracias! Tal vez agregaría submitPressed = false; después de popBackStackInmediate.
Neonigma
No utilicé el método public void onSaveInstanceState (Bundle outState). ¿Necesito establecer un método vacío para public void onSaveInstanceState (Bundle outState)?
Faxriddin Abdullayev
55

Compruebe si la actividad isFinishing()antes de mostrar el fragmento y preste atención commitAllowingStateLoss().

Ejemplo:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Naskov
fuente
1
! isFinishing () &&! isDestroyed () no funciona para mí.
Allen Vork
! isFinishing () &&! isDestroyed () funcionó para mí, pero requiere API 17. Pero simplemente no muestra a DialogFragment. Ver stackoverflow.com/questions/15729138/… para otras buenas soluciones, stackoverflow.com/a/41813953/2914140 me ayudó.
CoolMind
29

Es octubre de 2017, y Google crea la Biblioteca de soporte de Android con el nuevo componente llamado ciclo de vida. Proporciona una idea nueva para este problema 'No se puede realizar esta acción después de onSaveInstanceState'.

En breve:

  • Use el componente del ciclo de vida para determinar si es el momento correcto para mostrar su fragmento.

Versión más larga con explicar:

  • ¿Por qué sale este problema?

    Es porque está intentando utilizar FragmentManagersu actividad (¿qué va a contener su fragmento, supongo?) Para confirmar una transacción para su fragmento. Por lo general, esto parecería que está tratando de hacer alguna transacción para un fragmento próximo, mientras tanto, la actividad del host ya llama al savedInstanceStatemétodo (el usuario puede tocar el botón de inicio para que la actividad llame onStop(), en mi caso es la razón)

    Por lo general, este problema no debería suceder; siempre intentamos cargar el fragmento en la actividad desde el principio, como si el onCreate()método fuera el lugar perfecto para esto. Pero a veces esto sucede , especialmente cuando no puede decidir qué fragmento cargará en esa actividad, o si está tratando de cargar un fragmento desde un AsyncTaskbloque (o cualquier cosa tomará un poco de tiempo). El tiempo, antes de que la transacción del fragmento realmente ocurra, pero después del onCreate()método de la actividad , el usuario puede hacer cualquier cosa. Si el usuario presiona el botón de inicio, que activa el onSavedInstanceState()método de la actividad , se produciría un can not perform this actionbloqueo.

    Si alguien quiere ver más profundamente en este tema, sugiero que miren este blog posterior . Se ve profundamente dentro de la capa de código fuente y explica mucho al respecto. Además, da la razón por la que no debería usar el commitAllowingStateLoss()método para solucionar este bloqueo (créame, no ofrece nada bueno para su código)

  • ¿Cómo arreglar esto?

    • ¿Debo usar el commitAllowingStateLoss()método para cargar fragmentos? No, no deberías ;

    • ¿Debo anular el onSaveInstanceStatemétodo, ignorar el supermétodo dentro de él? No, no deberías ;

    • ¿Debo usar la isFinishingactividad mágica interna para verificar si la actividad del host está en el momento adecuado para una transacción fragmentada? Sí, esto parece la forma correcta de hacerlo.

  • Eche un vistazo a lo que puede hacer el componente Lifecycle .

    Básicamente, Google realiza algunas implementaciones dentro de la AppCompatActivityclase (y varias otras clases base que debe usar en su proyecto), lo que facilita la determinación del estado actual del ciclo de vida . Eche un vistazo a nuestro problema: ¿por qué sucedería este problema? Es porque hacemos algo en el momento equivocado. Así que tratamos de no hacerlo, y este problema desaparecerá.

    Codifico un poco para mi propio proyecto, esto es lo que hago usando LifeCycle. Codifico en Kotlin.

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

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

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

Como muestro arriba. Comprobaré el estado del ciclo de vida de la actividad del host. Con el componente Lifecycle dentro de la biblioteca de soporte, esto podría ser más específico. El código lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)significa, si el estado actual es al menos onResume, ¿no más tarde? Lo que asegura que mi método no se ejecutará durante algún otro estado de vida (como onStop).

  • ¿Ya está todo hecho?

    Por supuesto no. El código que he mostrado muestra una nueva forma de evitar que la aplicación se bloquee. Pero si va al estado de onStop, esa línea de código no hará cosas y, por lo tanto, no mostrará nada en la pantalla. Cuando los usuarios vuelvan a la aplicación, verán una pantalla vacía, esa es la actividad de host vacía que no muestra ningún fragmento. Es una mala experiencia (sí, un poco mejor que un accidente).

    Así que aquí desearía que pudiera haber algo más agradable: la aplicación no se bloqueará si se trata de un estado de vida posterior onResume, el método de transacción es consciente del estado de vida; Además, la actividad intentará continuar con esa acción de transacción fragmentada, después de que el usuario regrese a nuestra aplicación.

    Agrego algo más 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()
        }
    }
}

Mantengo una lista dentro de esta dispatcherclase, para almacenar esos fragmentos no tengo la oportunidad de finalizar la acción de la transacción. Y cuando el usuario regrese de la pantalla de inicio y descubra que todavía hay un fragmento esperando a ser lanzado, irá al resume()método bajo la @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)anotación. Ahora creo que debería funcionar como esperaba.

Anthonyeef
fuente
8
Sería bueno usar Java en lugar de Kotlin
Shchvova
1
¿Por qué su implementación de FragmentDispatcherutilizar una lista para almacenar fragmentos pendientes si solo se restaurará un fragmento?
fraherm
21

Aquí hay una solución diferente a este problema.

Al usar una variable miembro privada, puede establecer los datos devueltos como una intención que luego se puede procesar después de super.onResume ();

Al igual que:

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
fuente
77
Dependiendo de la acción que estaba haciendo que no estaba permitida, esto podría tener que pasar a un momento aún más tarde que onResume (). Para insatcne, si FragmentTransaction.commit () es el problema, debe ir a onPostResume () en su lugar.
pjv
1
Esta es LA respuesta a esta pregunta para mí. Como necesitaba reenviar una etiqueta NFC recibida a la actividad anterior, esto es lo que me ayudó.
Janis Peisenieks
77
Para mí estaba sucediendo porque no estaba llamando super.onActivityResult().
Sufian
20

Solución corta y funcional:

Siga pasos simples

Pasos

Paso 1: anular el onSaveInstanceStateestado en el fragmento respectivo. Y elimine el súper método.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Paso 2: uso fragmentTransaction.commitAllowingStateLoss( );

en lugar de fragmentTransaction.commit( ); fragmentar operaciones.

Vinayak
fuente
Respuesta no se copia o se forma arbitrada en otro lugar .IS se envió a la ayuda a las personas de mi solución de trabajo que se obtuvieron por varios ensayo y error
Vinayak
12

CUIDADO , el uso transaction.commitAllowingStateLoss()podría resultar en una mala experiencia para el usuario. Para obtener más información sobre por qué se produce esta excepción, consulte esta publicación .

Eric Brandwein
fuente
66
Esto no proporciona una respuesta a la pregunta, debe proporcionar una respuesta válida para la pregunta
Umar Ata
10

Encontré una solución sucia para este tipo de problema. Si aún desea conservar su ActivityGroupspor cualquier razón (tenía razones de limitación de tiempo), simplemente implemente

public void onBackPressed() {}

en su Activityy hacer algo de backcódigo allí. incluso si no existe tal método en dispositivos más antiguos, este método es llamado por los más nuevos.

jinete de sable
fuente
6

No use commitAllowingStateLoss (), solo debe usarse para casos en los que está bien que el estado de la IU cambie inesperadamente en el usuario.

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

Si la transacción ocurre en ChildFragmentManager de parentFragment, use parentFragment.isResume () afuera para verificar en su lugar.

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

Tuve un problema similar, el escenario era así:

  • Mi actividad está agregando / reemplazando fragmentos de la lista.
  • Cada fragmento de la lista tiene una referencia a la actividad, para notificar la actividad cuando se hace clic en un elemento de la lista (patrón de observador).
  • Cada fragmento de la lista llama a setRetainInstance (true); en su método onCreate .

El método onCreate de la actividad era así:

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

La excepción se produjo porque cuando la configuración cambia (dispositivo girado), se crea la actividad, el fragmento principal se recupera del historial del administrador de fragmentos y al mismo tiempo el fragmento ya tiene una referencia ANTIGUA a la actividad destruida

cambiando la implementación a esto resolvió el 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);

debe configurar sus oyentes cada vez que se crea la actividad para evitar la situación en la que los fragmentos tienen referencias a instancias destruidas de la actividad.

Mina Samy
fuente
5

Si hereda de FragmentActivity, debe llamar a la superclase en onActivityResult():

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

Si no hace esto e intenta mostrar un cuadro de diálogo de fragmento en ese método, puede obtener OP IllegalStateException. (Para ser honesto, no entiendo por qué la súper llamada soluciona el problema. onActivityResult()Se llama antes onResume(), por lo que aún no se debe permitir que muestre un cuadro de diálogo de fragmento).

Lawrence Kesteloot
fuente
1
Me encantaría saber por qué esto soluciona el problema.
Big McLargeHuge
3

Recibí esta excepción cuando presioné el botón Atrás para cancelar el selector de intención en mi actividad de fragmento de mapa. Resolví esto reemplazando el código de onResume (donde estaba inicializando el fragmento) por onstart () y la aplicación funciona bien. Espero que ayude.

DCS
fuente
2

Creo que usar transaction.commitAllowingStateLoss();no es la mejor solución. Esta excepción se generará cuando la configuración de la actividad cambie y onSavedInstanceState()se llame al fragmento y, posteriormente, su método de devolución de llamada asincrónica intente confirmar el fragmento.

Una solución simple podría ser verificar si la actividad está cambiando la configuración o no

por ejemplo, verificar isChangingConfigurations()

es decir

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

Mira este enlace también

Amol Desai
fuente
De alguna manera, obtuve esta excepción cuando el usuario hace clic en algo (el clic es el desencadenante para realizar la confirmación de transacción). ¿Cómo podría ser esto? ¿Su solución aquí?
Desarrollador de Android
@androiddeveloper, ¿qué más estás haciendo con el clic del usuario? de alguna manera fragmento es el ahorro de su estado antes de confirmar la transacción
Amol Desai
La excepción se produjo en la línea exacta de confirmación de transacción. Además, tuve un error tipográfico extraño: en lugar de "aquí aquí" quise decir "trabajar aquí".
Desarrollador de Android
@androiddeveloper tienes razón! pero antes de comprometer la transacción, ¿estás generando algún hilo de fondo o algo así?
Amol Desai
No lo creo (lo siento, estoy fuera de la oficina), pero ¿por qué sería importante? Aquí están todas las cosas de la interfaz de usuario ... Si alguna vez hago algo en un hilo de fondo, tendré excepciones allí, además no pongo cosas relacionadas con la interfaz de usuario en hilos de fondo, ya que es demasiado arriesgado.
Desarrollador de Android
2

Posiblemente la solución más sencilla y sencilla que encontré en mi caso fue evitar sacar el fragmento ofensivo de la pila en respuesta al resultado de la actividad. Entonces cambiando esta llamada en mi onActivityResult():

popMyFragmentAndMoveOn();

a esto:

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

ayudó en mi caso.

mojuba
fuente
2

Si está haciendo alguna FragmentTransaction en onActivityResult, lo que puede hacer puede establecer un valor booleano dentro de onActivityResult, luego en onResume puede hacer su FragmentTransaction en función del valor booleano. Por favor, consulte el código a continuación.

@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
fuente
No publique el código como una imagen, utilice el formato de código en su lugar.
Micer
1
Sí, me ayudó ... Esta es la solución exacta y adecuada para el dispositivo de malvavisco. Obtuve esta excepción y la resolví con este simple truco. De ahí arriba votó.
sandhya sasane
2

Cortesía: Solución para IllegalStateException

Este problema me había molestado durante mucho tiempo, pero afortunadamente encontré una solución concreta. Una explicación detallada de esto está aquí .

El uso de commitAllowStateloss () podría evitar esta excepción, pero provocaría irregularidades en la interfaz de usuario. Hasta ahora hemos entendido que se encuentra IllegalStateException cuando intentamos confirmar un fragmento después de que se pierde el estado de Actividad, por lo que deberíamos retrasar la transacción hasta que se restablezca el estado Se puede hacer simplemente así

Declara dos variables 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;

Ahora en onPostResume () y onPause establecemos y desarmamos nuestra variable booleana isTransactionSafe. La idea es marcar la trasnsacción como segura solo cuando la actividad está en primer plano para que no haya posibilidad de pérdida de estado.

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

-Lo que hemos hecho hasta ahora ahorrará desde IllegalStateException pero nuestras transacciones se perderán si se realizan después de que la actividad pase a segundo plano, algo así como commitAllowStateloss (). Para ayudar con eso tenemos la variable 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
fuente
2

¡Las transacciones de fragmentos no deben ejecutarse después Activity.onStop()! Verifique que no tenga ninguna devolución de llamada que pueda ejecutar la transacción después onStop(). Es mejor arreglar la razón en lugar de tratar de evitar el problema con enfoques como.commitAllowingStateLoss()

Ivo Stoyanov
fuente
1

A partir de la versión 24.0.0 de la biblioteca de soporte, puede llamar al FragmentTransaction.commitNow()método que confirma esta transacción sincrónicamente en lugar de llamar commit()seguido de executePendingTransactions(). Como dice la documentación , este enfoque es aún mejor:

Llamar a commitNow es preferible a llamar a commit () seguido de executePendingTransactions (), ya que este último tendrá el efecto secundario de intentar confirmar todas las transacciones actualmente pendientes, sea ese el comportamiento deseado o no.

Volodymyr
fuente
1

Cada vez que intente cargar un fragmento en su actividad, asegúrese de que la actividad se reanude y no vaya a pausar el estado. En el estado de pausa, puede terminar perdiendo la operación de confirmación que se realiza.

Puede usar transacción.commitAllowingStateLoss () en lugar de transacción.commit () para cargar el fragmento

o

Cree un booleano y verifique si la actividad no se va a pausar

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

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

luego, al cargar la comprobación de fragmentos

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

Para evitar este problema, podemos usar el Componente de Arquitectura de Navegación , que se introdujo en Google I / O 2018. El Componente de Arquitectura de Navegación simplifica la implementación de la navegación en una aplicación de Android.

Levon Petrosyan
fuente
No es lo suficientemente bueno y tiene errores (un mal manejo del enlace profundo, no puede guardar fragmentos de mostrar / ocultar estado y algún problema importante que aún está abierto)
do01
1

Con respecto a la gran respuesta de @Anthonyeef, aquí hay un código de muestra en Java:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

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

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

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

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}
Morza
fuente
1

Si tiene un bloqueo con el método popBackStack () o popBackStackImmediate (), intente fixt con:

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

Esto también funcionó para mí.

Tapa Save
fuente
Tenga en cuenta que requiere API 26 y superior
Itay Feldman
1

En mi caso, recibí este error en un método de anulación llamado onActivityResult. Después de excavar, acabo de darme cuenta de que tal vez tenía que llamar " super " antes.
Lo agregué y simplemente funcionó

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

}

Tal vez solo necesite agregar un 'super' en cualquier anulación que esté haciendo antes de su código.

Gian Gomen
fuente
1

Extensión 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
fuente
Puede eliminar el () -> antes del Fragmento
Claire
@Claire, gracias! ¿Te refieres a cambiar a fragment: Fragment? Sí, probé esta variante, pero en este caso se crearía un fragmento en todos los casos (incluso cuando fragmentManager == nulo, pero no encontré esta situación). Actualicé la respuesta y cambié nulo para etiquetar addToBackStack().
CoolMind
1

Este bloqueo se debe a que se ha cometido una FragmentTransaction después de que el ciclo de vida de la actividad propietaria ya se haya ejecutado enSaveInstanceState. Esto a menudo es causado por la confirmación de FragmentTransactions desde una devolución de llamada asincrónica. Consulte el recurso vinculado para más detalles.

Fragmento de transacciones y pérdida de actividad del estado

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

Bholendra Singh
fuente
0

Agregue esto a su actividad

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

También he experimentado este problema y el problema ocurre cada vez que FragmentActivityse cambia el contexto de usted (por ejemplo, se cambia la orientación de la pantalla, etc.). Entonces, la mejor solución es actualizar el contexto desde tu FragmentActivity.

Adán
fuente
0

Terminé creando un fragmento base y haciendo que todos los fragmentos de mi aplicación lo extiendan

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

Luego, cuando trato de mostrar un fragmento que uso en showAllowingStateLosslugar deshow

Me gusta esto:

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

Llegué a esta solución desde este PR: https://github.com/googlesamples/easypermissions/pull/170/files

Ahmad El-Melegy
fuente
0

Otra posible solución, que no estoy seguro si ayuda en todos los casos (origen aquí ):

@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();
        }
    }
}
desarrollador de Android
fuente
0

Sé que hay una respuesta aceptada por @Ovidiu Latcu, pero después de un tiempo, el error aún persiste.

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

Crashlytics todavía me envía este extraño mensaje de error.

Sin embargo, ahora se produce un error solo en la versión 7+ (Nougat). Mi solución fue usar commitAllowingStateLoss () en lugar de commit () en fragmentTransaction.

Esta publicación es útil para commitAllowingStateLoss () y nunca más tuvo un problema de fragmentos.

Para resumir, la respuesta aceptada aquí podría funcionar en las versiones de Android pre Nougat.

Esto podría ahorrarle a alguien unas horas de búsqueda. felices codificaciones <3 aplausos

ralphgabb
fuente