Pokaż DialogFragment z onActivityResult

82

Mam następujący kod w moim onActivityResult dla mojego fragmentu:

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

Jednak pojawia się następujący błąd:

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

Czy ktoś wie, co się dzieje lub jak mogę to naprawić? Powinienem zauważyć, że używam pakietu wsparcia dla Androida.

Kurtis Nusbaum
źródło

Odpowiedzi:

76

Jeśli korzystasz z biblioteki wsparcia Androida, metoda onResume nie jest odpowiednim miejscem do zabawy z fragmentami. Powinieneś to zrobić w metodzie onResumeFragments, patrz opis metody onResume: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

Tak więc poprawny kod z mojego punktu widzenia powinien wyglądać następująco:

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
źródło
13
+1, to jest poprawna odpowiedź. Zauważ, że onResumeFragments()nie istnieje w Activityklasie. Jeśli używasz wersji podstawowej Activity, powinieneś użyć onPostResume()zamiast niej.
Alex Lockwood,
3
Przed wdrożeniem tego rozwiązania przeczytaj to, aby zobaczyć, dlaczego jest to hack. W komentarzach do innego rozwiązania w tym pytaniu kryje się dużo prostsze rozwiązanie.
gałązka
1
Wywołanie super.onActivityResult nie zapobiega IllegalStateException, więc nie jest poprawką dla subj. problem
zobaczcie
1
To pytanie jest pierwszym trafieniem w Google dotyczącym tego problemu, ale przyjęta odpowiedź nie jest moim zdaniem najlepsza. Zamiast tego należy zaakceptować tę odpowiedź: stackoverflow.com/a/30429551/1226020
JHH
27

EDYCJA: Nie jest to błąd, ale raczej wada we frameworku fragmentów. Lepszą odpowiedzią na to pytanie jest ta, której udzielił powyżej @Arcao.

---- Oryginalny post ----

Właściwie jest to znany błąd w pakiecie wsparcia (edycja: właściwie nie jest to błąd. Zobacz komentarz @ alex-lockwood). Opublikowane obejście w komentarzach do zgłoszenia błędu polega na zmodyfikowaniu źródła DialogFragment w następujący sposób:

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

Zauważ, że to gigantyczny hack. Sposób, w jaki to zrobiłem, polegał na stworzeniu własnego fragmentu okna dialogowego, w którym mogłem się zarejestrować z oryginalnego fragmentu. Kiedy ten inny fragment dialogu zrobił coś (na przykład został odrzucony), powiedział wszystkim słuchaczom, że odchodzi. Zrobiłem to tak:

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

Teraz mam sposób powiadamiania PlayerListFragment, gdy coś się wydarzy. Zauważ, że bardzo ważne jest, abyś odpowiednio wywołał unregisterPasswordEnteredListener (w powyższym przypadku, gdy PlayerListFragment "znika"), w przeciwnym razie ten fragment okna dialogowego może próbować wywołać funkcje zarejestrowanego nasłuchiwania, gdy ten odbiornik już nie istnieje.

Kurtis Nusbaum
źródło
3
rozwiązanie, które nie wymaga kopiowania źródła ... po prostu zastąp show()i przechwyć plik IllegalStateException.
Jeffrey Blattman
1
Jak zmodyfikować źródło DialogFragment? Czy możesz opublikować swoje rozwiązanie wspomniane na końcu posta?
Piotr Ślesarew
1
@PeterSlesarew Opublikowałem moje (raczej konkretne) rozwiązanie.
Kurtis Nusbaum
9
Gah, to nie jest błąd! Struktura Androida celowo rzuca wyjątek, ponieważ wykonywanie transakcji fragmentarycznych wewnątrz nie jest bezpieczne onActivityResult()! Zamiast tego wypróbuj to rozwiązanie: stackoverflow.com/questions/16265733/ ...
Alex Lockwood
2
@AlexLockwood Dokumentacja nie ostrzegała o tym, gdy zadano to pytanie. Ponadto, chociaż twoje rozwiązanie wygląda teraz dobrze , nie działało w kwietniu 2012 r. onPostResumeI onResumeFragmentsoba są stosunkowo nowymi dodatkami do biblioteki pomocy technicznej.
godz.
24

Komentarz pozostawiony przez @Natix to szybka jedna linijka, którą niektórzy ludzie mogli usunąć.

Najprostszym rozwiązaniem tego problemu jest wywołanie super.onActivityResult () PRZED uruchomieniem własnego kodu. Działa to niezależnie od tego, czy korzystasz z biblioteki pomocy technicznej, czy nie, i utrzymuje spójność behawioralną w Twoim działaniu.

Jest:

Im więcej o tym czytam, tym bardziej szalone triki widzę.

Jeśli nadal napotykasz problemy, to ten autorstwa Alexa Lockwooda jest tym, który należy sprawdzić.

Gałązka
źródło
Co się stanie, jeśli masz spadek? Wywołanie super.onActivityResult () może być problemem, jeśli chcesz najpierw uruchomić kod przed wywołaniem super, super klasa może mieć własny kod wewnątrz onActivityResult, bądź ostrożny.
Ricard
Dodam super.onActivityResult(requestCode, resultCode, data)przed jakimkolwiek kodem, to rozwiązało mój problem. Ale dodając dziedziczenie lub nadpisując domyślną wartość onActivityResult, powinniśmy ręcznie obsłużyć onStart / onResume
mochadwi
14

Uważam, że to błąd Androida. Zasadniczo system Android wywołuje metodę onActivityResult w niewłaściwym punkcie cyklu życia działania / fragmentu (przed onStart ()).

Błąd został zgłoszony pod adresem https://issuetracker.google.com/issues/36929762

Rozwiązałem to, zasadniczo przechowując intencję jako parametr, który później przetworzyłem w onResume ().

[EDYTUJ] Obecnie są lepsze rozwiązania tego problemu, które nie były dostępne w 2012 roku. Zobacz pozostałe odpowiedzi.

hrnt
źródło
8
Właściwie to nie jest tak naprawdę błąd. Jak wskazano w komentarzach, wyraźnie zaznaczono, że onActivityResult()nazywa się to wcześniejonResume()
Kurtis Nusbaum
2
Czy przeczytałeś ostatni komentarz do błędu? Błąd polega na tym, że onActivityResult () jest wywoływana przed onStart (), a nie to, że jest wywoływana przed onResume ().
hrnt
Ach tak, to też prawda. Przegapiłem to. Chociaż nadal uważam, że inne zgłoszenie błędu jest trochę bardziej istotne dla mojego problemu.
Kurtis Nusbaum,
Jest dobrze zdefiniowany, gdy wywoływana jest metoda onActivityResult. Dlatego nie może to być błąd, nawet jeśli w niektórych przypadkach może wydawać się niewłaściwy.
sstn
1
@sstn, czy mógłbyś to rozwinąć? Jest dobrze zdefiniowana, gdy wywoływana jest metoda onActivityResult (= bezpośrednio przed onResume). Android nie wywołuje onActivityResult bezpośrednio przed onResume. Zatem jest to błąd.
hrnt
11

EDYCJA: Jeszcze inna opcja i prawdopodobnie najlepsza z dotychczasowych (lub przynajmniej tego, czego oczekuje biblioteka wsparcia ...)

Jeśli używasz DialogFragments z biblioteką obsługi systemu Android, powinieneś używać podklasy FragmentActivity. Spróbuj wykonać następujące czynności:

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
}

Przyjrzałem się źródłu FragmentActivity i wygląda na to, że wywołuje wewnętrznego menedżera fragmentów w celu wznowienia fragmentów bez utraty stanu.


Znalazłem rozwiązanie, którego nie ma na liście. Tworzę Handler i uruchamiam fragment dialogu w Handlerze. Więc trochę edytuj swój kod:

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
}

Wydaje mi się to czystsze i mniej hakerskie.

Simon Jacobs
źródło
5
Użycie Handlera do rozwiązania tego problemu tylko dodaje opóźnienie, przez co jest mniej prawdopodobne, że wystąpi problem. Ale nie gwarantuje to, że problem zniknie! To trochę jak rozwiązywanie warunków wyścigu za pomocą Thread#sleep().
Alex Lockwood,
27
Dzwonienie super.onActivityResult()jest najprostszym działającym rozwiązaniem, jakie istnieje i prawdopodobnie powinna być akceptowaną odpowiedzią! Przypadkowo zauważyłem brakujące połączenie super i byłem mile zaskoczony, że dodanie go po prostu zadziałało. Pozwoliło mi to usunąć jeden ze starych hacków wspomnianych na tej stronie (zapisanie okna dialogowego w zmiennej tymczasowej i wyświetlenie go w onResume()).
Natix
Niezłe rozwiązanie. Jedynym problemem jest to, że onActivityResult()nie zwraca żadnej wartości wskazującej, czy fragmenty obsłużyły wynik.
Michael
Wywołanie super.onActivityResult () nie rozwiązuje awarii IllegalStateException w moim projekcie
zobacz:
9

Istnieją dwie metody show () DialogFragment - show(FragmentManager manager, String tag)i show(FragmentTransaction transaction, String tag).

Jeśli chcesz użyć wersji metody FragmentManager (jak w pierwotnym pytaniu), prostym rozwiązaniem jest zastąpienie tej metody i użycie commitAllowingStateLoss:

public class MyDialogFragment extends DialogFragment {

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

}

Zastępowanie w show(FragmentTransaction, String)ten sposób nie jest tak łatwe, ponieważ powinno również zmodyfikować niektóre zmienne wewnętrzne w oryginalnym kodzie DialogFragment, więc nie polecałbym tego - jeśli chcesz użyć tej metody, wypróbuj sugestie w zaakceptowanej odpowiedzi (lub komentarz z Jeffrey Blattman).

Korzystanie z commitAllowingStateLoss wiąże się z pewnym ryzykiem - w dokumentacji jest napisane „Like commit ()”, ale zezwala na wykonanie zatwierdzenia po zapisaniu stanu aktywności. Jest to niebezpieczne, ponieważ zatwierdzenie może zostać utracone, jeśli działanie będzie później przywrócone ze stanu , więc powinno to być używane tylko w przypadkach, gdy stan interfejsu użytkownika może się nieoczekiwanie zmienić na użytkowniku. "

gkee
źródło
4

Nie można wyświetlić okna dialogowego po dołączonym działaniu wywołanym jego metodą onSaveInstanceState (). Oczywiście onSaveInstanceState () jest wywoływana przed onActivityResult (). Więc powinieneś pokazać swoje okno dialogowe w tej metodzie wywołania zwrotnego OnResumeFragment (), nie musisz nadpisywać metody show () DialogFragment. Mam nadzieję, że to ci pomoże.

handrenliang
źródło
3

Wymyśliłem trzecie rozwiązanie, częściowo oparte na rozwiązaniu hmt. Zasadniczo utwórz ArrayList of DialogFragments, które będą wyświetlane w 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");
}
PearsonArtPhoto
źródło
3

onActivityResult () wykonuje się przed onResume (). Musisz wykonać swój interfejs użytkownika w onResume () lub później.

Użyj wartości logicznej lub czegokolwiek innego, czego potrzebujesz, aby zakomunikować, że wynik powrócił między obiema tymi metodami.

... Otóż to. Prosty.

Eurig Jones
źródło
2

Wiem, że odpowiedź na to pytanie udzielono już jakiś czas temu .. ale jest na to znacznie łatwiejszy sposób niż niektóre inne odpowiedzi, które tu widziałem ... W moim konkretnym przypadku musiałem pokazać fragment DialogFragment z fragmentów onActivityResult () metoda.

To jest mój kod do obsługi tego i działa pięknie:

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

Jak wspomniano w niektórych innych postach, popełnienie błędu z utratą stanu może powodować problemy, jeśli nie jesteś ostrożny ... w moim przypadku po prostu wyświetlałem komunikat o błędzie użytkownikowi z przyciskiem do zamknięcia okna dialogowego, więc jeśli stan tego jest stracony, to nie jest wielka sprawa.

Mam nadzieję że to pomoże...

Justin
źródło
2

To stare pytanie, które rozwiązałem w najprostszy sposób, myślę:

getActivity().runOnUiThread(new Runnable() {
    @Override
        public void run() {
            MsgUtils.toast(getString(R.string.msg_img_saved),
                    getActivity().getApplicationContext());
        }
    });
LucasBatalha
źródło
2

Dzieje się tak, ponieważ po wywołaniu #onActivityResult () aktywność nadrzędna już wywołała #onSaveInstanceState ()

Użyłbym Runnable, aby "zapisać" akcję (pokaż okno dialogowe) na #onActivityResult (), aby użyć go później, gdy aktywność była gotowa.

Dzięki takiemu podejściu upewniamy się, że akcja, którą chcemy wykonać, zawsze zadziała

@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
źródło
0

Najczystsze rozwiązanie, które znalazłem, to:

@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
źródło
0

Otrzymałem ten błąd podczas wykonywania .show(getSupportFragmentManager(), "MyDialog");czynności.

Spróbuj .show(getSupportFragmentManager().beginTransaction(), "MyDialog");najpierw.

Jeśli nadal nie działa, ten post ( Pokaż DialogFragment z onActivityResult ) pomaga mi rozwiązać problem.

Youngjae
źródło
0

Inny sposób:

@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
źródło
0

po prostu zadzwoń super.onActivityResult(requestCode, resultCode, data);przed obsługą fragmentu

Thomas Klammer
źródło
-3

Jak wszyscy wiecie, ten problem jest spowodowany wywołaniem onActivityResult () przed onstart (), więc po prostu wywołaj onstart () na początku w onActivityResult (), tak jak to zrobiłem w tym kodzie

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}
Bunny Bandewar
źródło
Nigdy nie należy bezpośrednio wywoływać metod cyklu życia systemu Android. Te powinny być wywoływane tylko przez system.
Chantell Osejo