Mostrar DialogFragment desde onActivityResult

82

Tengo el siguiente código en mi onActivityResult para un fragmento mío:

onActivityResult(int requestCode, int resultCode, Intent data){
   //other code
   ProgressFragment progFragment = new ProgressFragment();  
   progFragment.show(getActivity().getSupportFragmentManager(), PROG_DIALOG_TAG);
   // other code
}

Sin embargo, recibo el siguiente error:

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState   

¿Alguien sabe qué está pasando o cómo puedo solucionarlo? Debo tener en cuenta que estoy usando el paquete de soporte de Android.

Kurtis Nusbaum
fuente

Respuestas:

75

Si usa la biblioteca de soporte de Android, el método onResume no es el lugar correcto para jugar con fragmentos. Debe hacerlo en el método onResumeFragments, consulte la descripción del método onResume: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

Entonces, el código correcto desde mi punto de vista debería ser:

private boolean mShowDialog = false;

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

  // remember that dialog should be shown
  mShowDialog = true;
}

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

  // play with fragments here
  if (mShowDialog) {
    mShowDialog = false;

    // Show only if is necessary, otherwise FragmentManager will take care
    if (getSupportFragmentManager().findFragmentByTag(PROG_DIALOG_TAG) == null) {
      new ProgressFragment().show(getSupportFragmentManager(), PROG_DIALOG_TAG);
    }
  }
}
Arcao
fuente
13
+1, esta es la respuesta correcta. Tenga en cuenta que onResumeFragments()no existe en la Activityclase. Si está utilizando un básico Activity, debería utilizar onPostResume()en su lugar.
Alex Lockwood
3
Antes de implementar esta solución, lea esto para ver por qué es un truco. Hay una solución mucho más simple oculta en los comentarios de otra solución en esta pregunta.
ramita
1
Llamar a super.onActivityResult no evita la IllegalStateException, por lo que no es una solución de un subj. problema
demaksee
1
Esta pregunta es la primera respuesta en Google para este problema, pero la respuesta aceptada no es la mejor en mi opinión. Esta respuesta debe aceptarse en su lugar: stackoverflow.com/a/30429551/1226020
JHH
27

EDITAR: No es un error, sino más bien una deficiencia en el marco de fragmentos. La mejor respuesta a esta pregunta es la proporcionada por @Arcao arriba.

---- Publicación original ----

En realidad, es un error conocido con el paquete de soporte (editar: en realidad no es un error. Ver el comentario de @ alex-lockwood). Una solución alternativa publicada en los comentarios del informe de error es modificar la fuente del DialogFragment de esta manera:

public int show(FragmentTransaction transaction, String tag) {
    return show(transaction, tag, false);
}


public int show(FragmentTransaction transaction, String tag, boolean allowStateLoss) {
    transaction.add(this, tag);
    mRemoved = false;
    mBackStackId = allowStateLoss ? transaction.commitAllowingStateLoss() : transaction.commit();
    return mBackStackId;
}

Tenga en cuenta que este es un truco gigante. La forma en que realmente lo hice fue crear mi propio fragmento de diálogo con el que pudiera registrarme desde el fragmento original. Cuando ese otro fragmento de diálogo hizo cosas (como ser descartado), le dijo a los oyentes que se iba. Lo hice así:

public static class PlayerPasswordFragment extends DialogFragment{

 Player toJoin;
 EditText passwordEdit;
 Button okButton;
 PlayerListFragment playerListFragment = null;

 public void onCreate(Bundle icicle){
   super.onCreate(icicle);
   toJoin = Player.unbundle(getArguments());
   Log.d(TAG, "Player id in PasswordFragment: " + toJoin.getId());
 }

 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle){
     View v = inflater.inflate(R.layout.player_password, container, false);
     passwordEdit = (EditText)v.findViewById(R.id.player_password_edit);
     okButton = (Button)v.findViewById(R.id.ok_button);
     okButton.setOnClickListener(new View.OnClickListener(){
       public void onClick(View v){
         passwordEntered();
       }
     });
     getDialog().setTitle(R.string.password_required);
     return v;
 }

 public void passwordEntered(){
   //TODO handle if they didn't type anything in
   playerListFragment.joinPlayer(toJoin, passwordEdit.getText().toString());
   dismiss();
 }

 public void registerPasswordEnteredListener(PlayerListFragment playerListFragment){
   this.playerListFragment = playerListFragment;
 }

 public void unregisterPasswordEnteredListener(){
   this.playerListFragment = null;
 }
}

Así que ahora tengo una forma de notificar al PlayerListFragment cuando suceden cosas. Tenga en cuenta que es muy importante que llame a unregisterPasswordEnteredListener de manera adecuada (en el caso anterior, cuando el PlayerListFragment "desaparece"), de lo contrario, este fragmento de diálogo podría intentar llamar a funciones en el oyente registrado cuando ese oyente ya no existe.

Kurtis Nusbaum
fuente
3
una solución que no requiere copiar la fuente ... simplemente anule show()y capture el IllegalStateException.
Jeffrey Blattman
1
¿Cómo modificar la fuente del DialogFragment? ¿O puede publicar su solución mencionada al final de su publicación?
Piotr Ślesarew
1
@PeterSlesarew Publiqué mi solución (bastante específica).
Kurtis Nusbaum
9
¡Gah, esto no es un error! ¡El marco de Android está lanzando la excepción a propósito porque no es seguro realizar transacciones de fragmentos adentro onActivityResult()! Pruebe esta solución en su lugar: stackoverflow.com/questions/16265733/…
Alex Lockwood
2
@AlexLockwood La documentación no advirtió sobre esto cuando se hizo esta pregunta. Además, aunque su solución se ve bien ahora , no funcionó en abril de 2012. onPostResumey onResumeFragmentsambas son adiciones relativamente nuevas a la biblioteca de soporte.
hrnt
24

El comentario dejado por @Natix es un breve resumen que algunas personas pueden haber eliminado.

La solución más simple a este problema es llamar a super.onActivityResult () ANTES de ejecutar su propio código. Esto funciona independientemente de si está utilizando la biblioteca de soporte o no y mantiene la coherencia de comportamiento en su Actividad.

Ahi esta:

Cuanto más leo sobre esto, más trucos locos he visto.

Si todavía tiene problemas, entonces el de Alex Lockwood es el que debe verificar.

ramita
fuente
¿Qué pasa si tienes herencia? Llamar a super.onActivityResult () podría ser un problema si primero desea ejecutar su código antes de llamar a super, super class podría tener su propio código dentro de onActivityResult, tenga cuidado.
Ricard
Estoy agregando super.onActivityResult(requestCode, resultCode, data)antes de cualquiera de mi código, solucionó mi problema. Pero al agregar herencia o anular el onActivityResult predeterminado, debemos manejar el onStart / onResume manualmente
mochadwi
14

Creo que es un error de Android. Básicamente, Android llama a onActivityResult en un punto incorrecto del ciclo de vida de la actividad / fragmento (antes de onStart ()).

El error se informa en https://issuetracker.google.com/issues/36929762

Lo resolví básicamente almacenando el Intent como un parámetro que luego procesé en onResume ().

[EDITAR] En la actualidad, existen mejores soluciones para este problema que no estaban disponibles en 2012. Vea las otras respuestas.

hrnt
fuente
8
En realidad, eso no es realmente un error. Como se señala en los comentarios allí, claramente establece que onActivityResult()se llama antesonResume()
Kurtis Nusbaum
2
¿Leíste el último comentario sobre el error? El error es que onActivityResult () se llama antes de onStart (), no que se llama antes de onResume ().
hrnt
Ah, sí, eso también es cierto. Me perdí eso. Aunque todavía creo que el otro informe de errores es un poco más relevante para mi problema.
Kurtis Nusbaum
Está bien definido cuando se llama a onActivityResult. Por lo tanto, no puede ser un error, incluso si puede parecer inapropiado en algunos casos.
sstn
1
@sstn, ¿podría darnos más detalles? Está bien definido cuando se llama a onActivityResult (= inmediatamente antes de onResume). Android no llama a onActivityResult inmediatamente antes de onResume. Por tanto, es un error.
2013
11

EDIT: Sin embargo, otra opción, y, posiblemente, el mejor hasta ahora (o, al menos lo que el apoyo Espera biblioteca ...)

Si está usando DialogFragments con la biblioteca de soporte de Android, debería usar una subclase de FragmentActivity. Intente lo siguiente:

onActivityResult(int requestCode, int resultCode, Intent data) {

   super.onActivityResult(requestCode, resultCode, intent);
   //other code

   ProgressFragment progFragment = new ProgressFragment();  
   progFragment.show(getActivity().getSupportFragmentManager(), PROG_DIALOG_TAG);

   // other code
}

Eché un vistazo a la fuente de FragmentActivity, y parece que está llamando a un administrador de fragmentos interno para reanudar los fragmentos sin perder el estado.


Encontré una solución que no figura aquí. Creo un controlador e inicio el fragmento de diálogo en el controlador. Entonces, editando tu código un poco:

onActivityResult(int requestCode, int resultCode, Intent data) {

   //other code

   final FragmentManager manager = getActivity().getSupportFragmentManager();
   Handler handler = new Handler();
   handler.post(new Runnable() {
       public void run() {
           ProgressFragment progFragment = new ProgressFragment();  
           progFragment.show(manager, PROG_DIALOG_TAG);
       }
   }); 

  // other code
}

Esto me parece más limpio y menos malicioso.

Simon Jacobs
fuente
5
El uso de un controlador para resolver este problema solo agrega un retraso, por lo que es más improbable que el problema ocurra. ¡Pero no garantiza que el problema desaparecerá! Es un poco como resolver las condiciones de carrera usando Thread#sleep().
Alex Lockwood
27
Llamar super.onActivityResult()es la solución de trabajo más simple que existe y probablemente debería ser la respuesta aceptada. Noté que faltaba la súper llamada por accidente y me sorprendió gratamente que agregarla simplemente funcionó. Esto me permitió eliminar uno de los antiguos trucos mencionados en esta página (guardar el cuadro de diálogo en una variable temporal y mostrarlo onResume()).
Natix
Buena solucion. El único problema es que onActivityResult()no devuelve ningún valor que indique si los fragmentos manejaron o no el resultado.
Michael
Llamar a super.onActivityResult () no soluciona el bloqueo de IllegalStateException en mi proyecto
demaksee
9

Hay dos métodos show () de DialogFragment - show(FragmentManager manager, String tag)yshow(FragmentTransaction transaction, String tag) .

Si desea usar la versión FragmentManager del método (como en la pregunta original), una solución fácil es anular este método y usar commitAllowingStateLoss:

public class MyDialogFragment extends DialogFragment {

  @Override 
  public void show(FragmentManager manager, String tag) {
      FragmentTransaction ft = manager.beginTransaction();
      ft.add(this, tag);
      ft.commitAllowingStateLoss();
  }

}

Primordial show(FragmentTransaction, String) esta forma no es tan fácil porque también debería modificar algunas variables internas dentro del código DialogFragment original, por lo que no lo recomendaría; si desea usar ese método, pruebe las sugerencias en la respuesta aceptada (o el comentario de Jeffrey Blattman).

Existe cierto riesgo al usar commitAllowingStateLoss - la documentación dice "Como commit () pero permite que el commit se ejecute después de que se guarda el estado de una actividad. Esto es peligroso porque el commit se puede perder si la actividad necesita ser restaurada posteriormente desde su estado , por lo que esto solo debe usarse en los casos en que está bien que el estado de la interfaz de usuario cambie inesperadamente en el usuario ".

gkee
fuente
4

No puede mostrar el diálogo después de la actividad adjunta llamada su método onSaveInstanceState (). Obviamente, onSaveInstanceState () se llama antes de onActivityResult (). Por lo tanto, debe mostrar su diálogo en este método de devolución de llamada OnResumeFragment (), no necesita anular el método show () de DialogFragment. Espero que esto te ayudará.

handrenliang
fuente
3

Se me ocurrió una tercera solución, basada parcialmente en la solución de hmt. Básicamente, cree una ArrayList de DialogFragments, que se mostrará en onResume ();

ArrayList<DialogFragment> dialogList=new ArrayList<DialogFragment>();

//Some function, like onActivityResults
{
    DialogFragment dialog=new DialogFragment();
    dialogList.add(dialog);
}


protected void onResume()
{
    super.onResume();
    while (!dialogList.isEmpty())
        dialogList.remove(0).show(getSupportFragmentManager(),"someDialog");
}
PearsonArteFoto
fuente
3

onActivityResult () se ejecuta antes que onResume (). Necesita hacer su IU en onResume () o posterior.

Utilice un booleano o lo que necesite para comunicar que se ha obtenido un resultado entre ambos métodos.

... Eso es. Sencillo.

Eurig Jones
fuente
2

Sé que esto fue respondido hace bastante tiempo ... pero hay una manera mucho más fácil de hacer esto que algunas de las otras respuestas que vi aquí ... En mi caso específico, necesitaba mostrar un DialogFragment de un fragmento onActivityResult () método.

Este es mi código para manejar eso, y funciona muy bien:

DialogFragment myFrag; //Don't forget to instantiate this
FragmentTransaction trans = getActivity().getSupportFragmentManager().beginTransaction();
trans.add(myFrag, "MyDialogFragmentTag");
trans.commitAllowingStateLoss();

Como se mencionó en algunas de las otras publicaciones, comprometerse con la pérdida de estado puede causar problemas si no tiene cuidado ... en mi caso, simplemente estaba mostrando un mensaje de error al usuario con un botón para cerrar el cuadro de diálogo, así que si el estado de que está perdido no es gran cosa.

Espero que esto ayude...

Justin
fuente
2

Es una vieja pregunta pero la he resuelto de la manera más sencilla, creo:

getActivity().runOnUiThread(new Runnable() {
    @Override
        public void run() {
            MsgUtils.toast(getString(R.string.msg_img_saved),
                    getActivity().getApplicationContext());
        }
    });
LucasBatalha
fuente
2

Sucede porque cuando se llama a #onActivityResult (), la actividad principal ya ha llamado a #onSaveInstanceState ()

Usaría un Runnable para "guardar" la acción (mostrar diálogo) en #onActivityResult () para usarlo más tarde cuando la actividad esté lista.

Con este enfoque nos aseguramos de que la acción que queremos siempre funcione

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == YOUR_REQUEST_CODE) {
        mRunnable = new Runnable() {
            @Override
            public void run() {
                showDialog();
            }
        };
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

@Override
public void onStart() {
    super.onStart();
    if (mRunnable != null) {
        mRunnable.run();
        mRunnable = null;
    }
}
Ricard
fuente
0

La solución más limpia que encontré es esta:

@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            onActivityResultDelayed(requestCode, resultCode, data);
        }
    });
}

public void onActivityResultDelayed(int requestCode, int resultCode, Intent data) {
    // Move your onActivityResult() code here.
}
fhucho
fuente
0

Recibí este error mientras estaba .show(getSupportFragmentManager(), "MyDialog");en actividad.

Intente .show(getSupportFragmentManager().beginTransaction(), "MyDialog");primero.

Si aún no funciona, esta publicación ( Show DialogFragment from onActivityResult ) me ayuda a resolver el problema.

Youngjae
fuente
0

De otra manera:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case Activity.RESULT_OK:
            new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message m) {
                    showErrorDialog(msg);
                    return false;
                }
            }).sendEmptyMessage(0);
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
    }
}


private void showErrorDialog(String msg) {
    // build and show dialog here
}
Maher Abuthraa
fuente
0

solo llama super.onActivityResult(requestCode, resultCode, data);antes de manejar el fragmento

Thomas Klammer
fuente
-3

Como todos saben, este problema se debe a que onActivityResult () está llamando antes de onstart (), así que simplemente llame a onstart () al inicio en onActivityResult () como lo he hecho en este código

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}
Bunny Bandewar
fuente
Usted debe no llamar a los métodos del ciclo de vida de Android directamente. Solo el sistema debe llamarlos.
Chantell Osejo