Показати DialogFragment з onActivityResult


82

У моєму onActivityResult для мого фрагмента є такий код:

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

Однак я отримую таку помилку:

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

Хтось знає, що відбувається, або як я можу це виправити? Слід зазначити, що я використовую пакет підтримки Android.

Відповіді:


75

Якщо ви використовуєте бібліотеку підтримки Android, метод onResume не в тому місці, де можна грати з фрагментами. Ви повинні зробити це в методі onResumeFragments, див. Опис методу onResume: http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume%28%29

Тож правильний код з моєї точки зору повинен бути:

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

13
+1, це правильна відповідь. Зверніть увагу, що onResumeFragments()цього Activityкласу не існує . Якщо ви використовуєте базовий Activity, onPostResume()замість цього слід використовувати .
Алекс Локвуд,

3
Перш ніж застосовувати це рішення, прочитайте його, щоб зрозуміти, чому це хакерство. У коментарях до іншого рішення цього питання є набагато простіше рішення.
гілочка

1
Виклик super.onActivityResult не заважає IllegalStateException, тому не є виправленням subj. проблема
demaksee

1
Це питання є першим зверненням до Google за цією проблемою, але прийнята відповідь не найкраща, на мій погляд. Відповідь на це питання має бути прийнятий замість: stackoverflow.com/a/30429551/1226020
JHH

27

EDIT: Не помилка, а більший недолік у структурі фрагментів. Кращою відповіддю на це питання є відповідь, надана @Arcao вище.

---- Оригінальний пост ----

Насправді це відома помилка з пакетом підтримки (редагувати: насправді не помилка. Див. Коментар @ alex-lockwood). Опублікована робота в коментарях до звіту про помилку має змінити джерело DialogFragment так:

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

Зверніть увагу, що це гігантський хак. Я насправді це зробив, просто створив власний діалоговий фрагмент, з яким я міг зареєструватися з оригінального фрагмента. Коли той інший фрагмент діалогу робив щось (наприклад, був відхилений), він повідомляв будь-якому слухачеві, що він зникає. Я зробив це так:

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

Тож тепер у мене є спосіб повідомити PlayerListFragment, коли щось трапиться. Зверніть увагу, що дуже важливо, щоб ви належним чином викликали unregisterPasswordEnteredListener (у наведеному вище випадку, коли "PlayerListFragment" коли-небудь "зникає"), інакше цей діалоговий фрагмент може спробувати викликати функції зареєстрованого слухача, коли його більше не існує.


3
рішення, яке не вимагає копіювання джерела ... просто перевизначте show()і впіймайте IllegalStateException.
Джеффрі Блаттман,

1
Як змінити джерело DialogFragment? Або ви можете опублікувати своє рішення, згадане в кінці вашого допису?
Piotr Ślesarew

1
@PeterSlesarew Я опублікував своє (досить конкретне) рішення.
Куртіс Нусбаум

9
Га, це не помилка! Фреймворк Android спеціально викидає виняток, оскільки виконувати фрагментаційні транзакції всередині небезпечно onActivityResult()! Спробуйте замість цього рішення: stackoverflow.com/questions/16265733/…
Алекс Локвуд,

2
@AlexLockwood Документація не попереджала про це, коли було задано це питання. Крім того, хоча ваше рішення виглядає добре зараз , воно не працювало ще в квітні 2012 року onPostResumeі onResumeFragmentsє відносно новими доповненнями до бібліотеки підтримки.
hrnt

24

Коментар, залишений @Natix, - це швидкий однокласник, який деякі люди, можливо, видалили.

Найпростішим рішенням цієї проблеми є виклик super.onActivityResult () ДО того, як запустити власний код. Це працює незалежно від того, використовуєте ви бібліотеку підтримки чи ні, і зберігає послідовність поведінки у Вашій діяльності.

Є:

Чим більше я читаю це, тим більше шалених хаків я бачив.

Якщо ви все ще стикаєтесь з проблемами, тоді слід перевірити Алекса Локвуда .


Що станеться, якщо ви отримали спадщину? Виклик super.onActivityResult () може бути проблемою, якщо ви хочете спочатку запустити свій код перед викликом super, супер клас може мати власний код всередині onActivityResult, будьте обережні.
Рікард

Я додаю super.onActivityResult(requestCode, resultCode, data)до будь-якого мого коду, це вирішило мою проблему. Але коли додаємо спадщину або замінюємо значення за замовчуванням onActivityResult, ми повинні обробляти onStart / onResume вручну
mochadwi

14

Я вважаю, що це помилка Android. В основному Android викликає onActivityResult у неправильний момент життєвого циклу активності / фрагмента (до onStart ()).

Про помилку повідомляється на https://issuetracker.google.com/issues/36929762

Я вирішив це, в основному зберігаючи Intent як параметр, який я згодом обробив у onResume ().

[РЕДАКТУВАТИ] На сьогодні є кращі рішення для цього питання, які не були доступні ще в 2012 році. Дивіться інші відповіді.


8
Насправді це насправді не помилка. Як зазначалося в тамтешніх коментарях, там чітко зазначено, що onActivityResult()називається ранішеonResume()
Куртіс Нусбаум,

2
Чи читали ви останній коментар до вади? Помилка полягає в тому, що onActivityResult () викликається перед onStart (), а не в тому, що він викликається перед onResume ().
hrnt

Ах так, це теж правда. Пропустив це. Хоча я все ще вважаю, що інший звіт про помилку трохи більше відповідає моїй проблемі.
Куртіс Нусбаум,

Це чітко визначено, коли викликається onActivityResult. Тому це не може бути помилка, навіть якщо в деяких випадках це може здатися недоречним.
sstn

1
@sstn, ти не міг би пояснити? Це чітко визначено, коли викликається onActivityResult (= безпосередньо перед onResume). Android не викликає onActivityResult безпосередньо перед onResume. Таким чином, це помилка.
hrnt

11

EDIT: Ще один варіант, і, можливо, найкращий ще (або, принаймні те, що очікує бібліотека підтримки ...)

Якщо ви використовуєте DialogFragments з бібліотекою підтримки Android, вам слід використовувати підклас FragmentActivity. Спробуйте наступне:

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
}

Я подивився джерело для FragmentActivity, і схоже, що він викликає внутрішній менеджер фрагментів, щоб відновити фрагменти без втрати стану.


Я знайшов рішення, якого тут немає. Я створюю обробник і запускаю фрагмент діалогу в обробнику. Отже, трохи відредагувавши свій код:

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
}

Мені це здається чистішим і менш удачним.


5
Використання обробника для вирішення цієї проблеми лише додає затримки, отже, стає менш імовірним, що проблема трапиться. Але це не гарантує, що проблема зникне! Це трохи як вирішення умов перегонів за допомогою Thread#sleep().
Alex Lockwood

27
Телефонування super.onActivityResult()- це найпростіше робоче рішення, яке існує, і це, мабуть, має бути прийнятою відповіддю! Я випадково помітив пропущений супердзвінок і був приємно здивований тим, що додавання його просто спрацювало. Це дозволило мені видалити один із старих хаків, згаданих на цій сторінці (збереження діалогового вікна у тимчасову змінну та показ у ньому onResume()).
Natix

Гарне рішення. Єдина проблема полягає в тому, onActivityResult()що не повертається жодне значення, яке вказує, чи обробляли фрагменти результат.
Майкл

Виклик super.onActivityResult () не усуває аварію IllegalStateException у моєму проекті
demaksee

9

Існує два методи DialogFragment show () - show(FragmentManager manager, String tag)і show(FragmentTransaction transaction, String tag).

Якщо ви хочете використовувати версію методу FragmentManager (як у вихідному питанні), просте рішення - перевизначити цей метод і використовувати commitAllowingStateLoss:

public class MyDialogFragment extends DialogFragment {

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

}

Перевизначення show(FragmentTransaction, String) цей спосіб не так просто, оскільки він також повинен змінити деякі внутрішні змінні в оригінальному коді DialogFragment, тому я б не рекомендував цього - якщо ви хочете використовувати цей метод, спробуйте пропозиції у прийнятій відповіді (або коментарі від Джеффрі Блаттман).

Існує певний ризик у використанні commitAllowingStateLoss - у документації зазначено "Подібно коміту (), але дозволяє виконувати коміт після збереження стану активності. Це небезпечно, оскільки коміт може бути втрачений, якщо діяльність пізніше потрібно відновити зі свого стану , тому це слід використовувати лише у тих випадках, коли стан користувальницького інтерфейсу може змінюватись несподівано для користувача. "


4

Ви не можете показати діалогове вікно після вкладеної діяльності, яка називається його методом onSaveInstanceState (). Очевидно, що onSaveInstanceState () викликається перед onActivityResult (). Тому ви повинні показати своє діалогове вікно в цьому методі зворотного виклику OnResumeFragment (), вам не потрібно перевизначати метод show () DialogFragment. Сподіваюся, це допоможе вам.


3

Я придумав третє рішення, частково засноване на рішенні hmt. По суті, створіть ArrayList з DialogFragments, який буде показано на 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");
}

3

onActivityResult () виконується перед onResume (). Вам потрібно зробити свій інтерфейс користувача в onResume () або пізнішої версії.

Використовуйте логічне значення або будь-яке інше, що вам потрібно, щоб повідомити, що результат повернувся між обома цими методами.

... Це воно. Простий.


2

Я знаю, що на це було дано відповідь досить давно .. але є набагато простіший спосіб зробити це, ніж деякі інші відповіді, які я бачив тут ... У моєму конкретному випадку мені потрібно було показати DialogFragment з фрагментів onActivityResult () метод.

Це мій код для обробки цього, і він працює чудово:

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

Як було згадано в деяких інших публікаціях, здійснення з втратою стану може спричинити проблеми, якщо ви не будете обережні ... у моєму випадку я просто відображав користувачеві повідомлення про помилку кнопкою, щоб закрити діалогове вікно, тому якщо стан втраченого не є великою проблемою.

Сподіваюся, це допомагає ...


2

Це давнє запитання, але я вирішив найпростішим способом, я думаю:

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

2

Це трапляється тому, що коли викликається #onActivityResult (), батьківська активність вже викликає #onSaveInstanceState ()

Я б використовував Runnable, щоб "зберегти" дію (показати діалогове вікно) на #onActivityResult (), щоб використовувати її пізніше, коли діяльність буде готова.

За допомогою цього підходу ми гарантуємо, що дія, яку ми хочемо, завжди буде працювати

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

0

Найчистішим рішенням, яке я знайшов, є таке:

@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.
}

0

Я отримав цю помилку під час .show(getSupportFragmentManager(), "MyDialog");роботи.

Спробуйте .show(getSupportFragmentManager().beginTransaction(), "MyDialog");спочатку.

Якщо все ще не працює, ця публікація ( Показати DialogFragment із onActivityResult ) допомагає мені вирішити проблему.


0

Інший спосіб:

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


-3

Як ви всі знаєте, ця проблема через те, що onActivityResult () викликає перед onstart (), тому просто зателефонуйте onstart () на початку в onActivityResult (), як це я зробив у цьому коді

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}

Ніколи не слід безпосередньо викликати методи життєвого циклу Android. Їх повинна викликати лише система.
Chantell Osejo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.