DialogFragment von onActivityResult anzeigen

82

Ich habe den folgenden Code in meinem onActivityResult für ein Fragment von mir:

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

Ich erhalte jedoch den folgenden Fehler:

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

Weiß jemand, was los ist oder wie ich das beheben kann? Ich sollte beachten, dass ich das Android Support Package verwende.

Kurtis Nusbaum
quelle

Antworten:

75

Wenn Sie die Android-Unterstützungsbibliothek verwenden, ist die onResume-Methode nicht der richtige Ort, um mit Fragmenten zu spielen. Sie sollten dies in der onResumeFragments-Methode tun, siehe Beschreibung der onResume-Methode: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

Aus meiner Sicht sollte der richtige Code also lauten:

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
quelle
13
+1, das ist die richtige Antwort. Beachten Sie, dass onResumeFragments()dies in der ActivityKlasse nicht vorhanden ist . Wenn Sie ein Basic verwenden Activity, sollten Sie onPostResume()stattdessen verwenden.
Alex Lockwood
3
Bevor Sie diese Lösung implementieren, lesen Sie diese bitte, um herauszufinden , warum es sich um einen Hack handelt. In den Kommentaren einer anderen Lösung in dieser Frage ist eine viel einfachere Lösung verborgen.
Zweig
1
Das Aufrufen von super.onActivityResult verhindert nicht die IllegalStateException, ist also kein Fix für ein Subjekt. Problem
Demaksee
1
Diese Frage ist der erste Treffer bei Google für dieses Problem, aber die akzeptierte Antwort ist meiner Meinung nach nicht die beste. Diese Antwort sollte stattdessen akzeptiert werden: stackoverflow.com/a/30429551/1226020
JHH
27

EDIT: Kein Fehler, sondern eher ein Mangel im Fragment-Framework. Die bessere Antwort auf diese Frage ist die von @Arcao oben.

---- Originaler Beitrag ----

Eigentlich ist es ein bekannter Fehler mit dem Support-Paket (bearbeiten: eigentlich kein Fehler. Siehe @ alex-lockwoods Kommentar). Eine in den Kommentaren des Fehlerberichts veröffentlichte Problemumgehung besteht darin, die Quelle des DialogFragments wie folgt zu ändern:

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

Beachten Sie, dass dies ein riesiger Hack ist. So wie ich es tatsächlich gemacht habe, habe ich einfach mein eigenes Dialogfragment erstellt, bei dem ich mich aus dem Originalfragment registrieren konnte. Wenn dieses andere Dialogfragment Dinge tat (wie entlassen werden), sagte es allen Zuhörern, dass es verschwinden würde. Ich habe es so gemacht:

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

Jetzt habe ich eine Möglichkeit, das PlayerListFragment zu benachrichtigen, wenn etwas passiert. Beachten Sie, dass es sehr wichtig ist, dass Sie unregisterPasswordEnteredListener entsprechend aufrufen (im obigen Fall, wenn das PlayerListFragment "verschwindet"). Andernfalls versucht dieses Dialogfragment möglicherweise, Funktionen für den registrierten Listener aufzurufen, wenn dieser Listener nicht mehr vorhanden ist.

Kurtis Nusbaum
quelle
3
Eine Lösung, bei der die Quelle nicht kopiert werden muss ... einfach überschreiben show()und abfangen IllegalStateException.
Jeffrey Blattman
1
Wie ändere ich die Quelle des DialogFragments? Oder können Sie Ihre am Ende Ihres Beitrags erwähnte Lösung veröffentlichen?
Piotr Ślesarew
1
@PeterSlesarew Ich habe meine (ziemlich spezifische) Lösung veröffentlicht.
Kurtis Nusbaum
9
Gah, das ist kein Fehler! Das Android-Framework löst die Ausnahme absichtlich aus, da es nicht sicher ist, Fragmenttransaktionen im Inneren auszuführen onActivityResult()! Versuchen Sie stattdessen diese Lösung: stackoverflow.com/questions/16265733/…
Alex Lockwood
2
@AlexLockwood Die Dokumentation warnte nicht davor, als diese Frage gestellt wurde. Darüber hinaus , während die Lösung sieht gut jetzt , es hat nicht 2012 in April arbeiten wieder onPostResumeund onResumeFragmentssind beide relativ neue Ergänzungen der Support - Bibliothek.
8.
24

Der Kommentar von @Natix ist ein schneller Einzeiler , den einige Leute möglicherweise entfernt haben.

Die einfachste Lösung für dieses Problem besteht darin, super.onActivityResult () aufzurufen, bevor Sie Ihren eigenen Code ausführen. Dies funktioniert unabhängig davon, ob Sie die Support-Bibliothek verwenden oder nicht, und behält die Verhaltenskonsistenz in Ihrer Aktivität bei.

Es gibt:

Je mehr ich darüber lese, desto mehr verrückte Hacks habe ich gesehen.

Wenn Sie immer noch auf Probleme stoßen, sollten Sie das von Alex Lockwood überprüfen.

Zweig
quelle
1
Ich habe super.onActivityResult () aufgerufen, war aber die letzte Zeile der Methode, und das Problem besteht weiterhin. Die Lösung besteht darin, zuerst super.onActivityResult () aufzurufen und dann den Rest der Dinge in der Methode zu erledigen.
Heloisasim
1
Ich habe nicht vergessen anzurufen super.onActivityResult()und sehe leider immer noch die Ausnahme. Wenn ich mir die tatsächliche Implementierung anschaue, ist sie tatsächlich leer (in Fragment.java, v25.0.0), sodass ich nicht sehe, wie sie irgendetwas lösen könnte. Vielleicht ist diese Antwort zu alt, sie hat früher funktioniert, aber nicht mehr?
BoD
1
Rufen Sie super.onActivityResult () vor Ihrem eigenen Code auf.
Zweig
Mein Problem verschwand, nachdem super.onActivityResult vor meinem Code verschoben wurde. Dies sollte wahrscheinlich in der Antwort erwähnt werden! Vielen Dank!
JHH
Danke Mann, das Hinzufügen von super.onActivityResult () hat geholfen, das Problem zu beheben.
Parth Bhuva
14

Ich glaube, es ist ein Android-Fehler. Grundsätzlich ruft Android onActivityResult an einer falschen Stelle im Aktivitäts- / Fragmentlebenszyklus auf (vor onStart ()).

Der Fehler wird unter https://issuetracker.google.com/issues/36929762 gemeldet

Ich habe es gelöst, indem ich den Intent im Grunde genommen als Parameter gespeichert habe, den ich später in onResume () verarbeitet habe.

[EDIT] Es gibt heutzutage bessere Lösungen für dieses Problem, die 2012 noch nicht verfügbar waren. Siehe die anderen Antworten.

hrnt
quelle
8
Eigentlich ist das kein wirklicher Fehler. Wie in den Kommentaren dort ausgeführt, heißt es deutlich, dass onActivityResult()vorher angerufen wurdeonResume()
Kurtis Nusbaum
2
Hast du den letzten Kommentar zum Fehler gelesen? Der Fehler ist, dass onActivityResult () vor onStart () aufgerufen wird, nicht dass es vor onResume () aufgerufen wird.
hrnt
Ah ja, das stimmt auch. Hab das verpasst. Obwohl ich immer noch glaube, dass der andere Fehlerbericht für mein Problem etwas relevanter ist.
Kurtis Nusbaum
Es ist genau definiert, wann onActivityResult aufgerufen wird. Daher kann es kein Fehler sein, auch wenn er in einigen Fällen unangemessen erscheint.
sstn
1
@sstn, könnten Sie näher darauf eingehen? Es ist genau definiert, wann onActivityResult aufgerufen wird (= unmittelbar vor onResume). Android ruft onActivityResult nicht unmittelbar vor onResume auf. Somit ist es ein Fehler.
15.
11

EDIT: Noch eine Option und möglicherweise die bisher beste (oder zumindest was die Support-Bibliothek erwartet ...)

Wenn Sie DialogFragments mit der Android-Unterstützungsbibliothek verwenden, sollten Sie eine Unterklasse von FragmentActivity verwenden. Versuche Folgendes:

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
}

Ich habe mir die Quelle für FragmentActivity angesehen und es sieht so aus, als würde ein interner Fragmentmanager aufgerufen, um Fragmente wieder aufzunehmen, ohne den Status zu verlieren.


Ich habe eine Lösung gefunden, die hier nicht aufgeführt ist. Ich erstelle einen Handler und starte das Dialogfragment im Handler. Bearbeiten Sie Ihren Code also ein wenig:

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
}

Das scheint mir sauberer und weniger hackig zu sein.

Simon Jacobs
quelle
5
Die Verwendung eines Handlers zur Lösung dieses Problems führt nur zu einer Verzögerung, wodurch es unwahrscheinlicher wird, dass das Problem auftritt. Aber es garantiert nicht, dass das Problem verschwinden wird! Es ist ein bisschen wie das Lösen von Rennbedingungen mit Thread#sleep().
Alex Lockwood
27
Anrufen super.onActivityResult()ist die einfachste funktionierende Lösung, die es gibt, und es sollte wahrscheinlich die akzeptierte Antwort sein! Ich habe den fehlenden Superanruf zufällig bemerkt und war angenehm überrascht, dass das Hinzufügen einfach funktioniert hat. Dadurch konnte ich einen der auf dieser Seite erwähnten alten Hacks entfernen (den Dialog in einer temporären Variablen speichern und in anzeigen onResume()).
Natix
Schöne Lösung. Das einzige Problem ist dasonActivityResult() kein Wert zurückgegeben wird, der angibt, ob Fragmente das Ergebnis verarbeitet haben oder nicht.
Michael
Das Aufrufen von super.onActivityResult () behebt den Absturz von IllegalStateException in meinem Projekt nicht
demaksee
9

Es gibt zwei DialogFragment show () -Methoden - show(FragmentManager manager, String tag)und show(FragmentTransaction transaction, String tag).

Wenn Sie die FragmentManager-Version der Methode verwenden möchten (wie in der ursprünglichen Frage), besteht eine einfache Lösung darin, diese Methode zu überschreiben und commitAllowingStateLoss zu verwenden:

public class MyDialogFragment extends DialogFragment {

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

}

Das Überschreiben auf show(FragmentTransaction, String)diese Weise ist nicht so einfach, da auch einige interne Variablen im ursprünglichen DialogFragment-Code geändert werden sollten. Daher würde ich es nicht empfehlen. Wenn Sie diese Methode verwenden möchten, probieren Sie die Vorschläge in der akzeptierten Antwort (oder im Kommentar von) aus Jeffrey Blattman).

Die Verwendung von commitAllowingStateLoss birgt ein gewisses Risiko. In der Dokumentation heißt es "Wie commit (), ermöglicht jedoch die Ausführung des Commits, nachdem der Status einer Aktivität gespeichert wurde. Dies ist gefährlich, da das Commit verloren gehen kann, wenn die Aktivität später aus ihrem Status wiederhergestellt werden muss Daher sollte dies nur in Fällen verwendet werden, in denen es in Ordnung ist, dass sich der UI-Status für den Benutzer unerwartet ändert. "

Gkee
quelle
4

Sie können keine Dialoge nach angehängten Aktivitäten anzeigen, die als Methode onSaveInstanceState () bezeichnet werden. Offensichtlich wird onSaveInstanceState () vor onActivityResult () aufgerufen. Sie sollten Ihren Dialog also in dieser Rückrufmethode OnResumeFragment () anzeigen. Sie müssen die show () -Methode von DialogFragment nicht überschreiben. Hoffe das wird dir helfen.

handrenliang
quelle
3

Ich habe eine dritte Lösung gefunden, die teilweise auf der Lösung von hmt basiert. Erstellen Sie grundsätzlich eine ArrayList von DialogFragments, die auf onResume () angezeigt wird.

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

onActivityResult () wird vor onResume () ausgeführt. Sie müssen Ihre Benutzeroberfläche in onResume () oder höher ausführen.

Verwenden Sie einen Booleschen Wert oder was auch immer Sie benötigen, um mitzuteilen, dass zwischen diesen beiden Methoden ein Ergebnis zurückgekehrt ist.

... Das ist es. Einfach.

Eurig Jones
quelle
2

Ich weiß, dass dies vor einiger Zeit beantwortet wurde. Aber es gibt einen viel einfacheren Weg, dies zu tun als einige der anderen Antworten, die ich hier gesehen habe. In meinem speziellen Fall musste ich ein DialogFragment aus einem Fragment von onActivityResult () anzeigen. Methode.

Dies ist mein Code, um damit umzugehen, und er funktioniert wunderbar:

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

Wie in einigen anderen Beiträgen erwähnt, kann das Festlegen eines Statusverlusts zu Problemen führen, wenn Sie nicht vorsichtig sind. In meinem Fall habe ich dem Benutzer lediglich eine Fehlermeldung mit einer Schaltfläche zum Schließen des Dialogfelds angezeigt Zustand davon ist verloren, es ist keine große Sache.

Hoffe das hilft...

Justin
quelle
2

Es ist eine alte Frage, aber ich habe sie auf einfachste Weise gelöst, denke ich:

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

Dies liegt daran, dass beim Aufrufen von #onActivityResult () die übergeordnete Aktivität bereits #onSaveInstanceState () aufgerufen hat.

Ich würde ein Runnable verwenden, um die Aktion (Dialogfeld anzeigen) auf #onActivityResult () zu "speichern", um sie später zu verwenden, wenn die Aktivität fertig ist.

Mit diesem Ansatz stellen wir sicher, dass die Aktion, die wir möchten, immer funktioniert

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

Die sauberste Lösung, die ich gefunden habe, ist folgende:

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

Ich habe diesen Fehler während der .show(getSupportFragmentManager(), "MyDialog");Aktivität erhalten.

Versuchen Sie es .show(getSupportFragmentManager().beginTransaction(), "MyDialog");zuerst.

Wenn dies immer noch nicht funktioniert, hilft mir dieser Beitrag ( DialogFragment von onActivityResult anzeigen ), das Problem zu lösen.

Youngjae
quelle
0

Ein anderer Weg:

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

Rufen Sie einfach an, super.onActivityResult(requestCode, resultCode, data);bevor Sie das Fragment bearbeiten

Thomas Klammer
quelle
-2

Wie Sie alle wissen, liegt dieses Problem daran, dass onActivityResult () vor onstart () aufgerufen wird. Rufen Sie also einfach onstart () beim Start in onActivityResult () auf, wie ich es in diesem Code getan habe

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}
Hase Bandewar
quelle
Sie sollten Android-Lebenszyklusmethoden niemals direkt aufrufen. Diese sollten nur vom System aufgerufen werden.
Chantell Osejo