Показать фрагмент диалога из onActivityResult

У меня есть следующий код в моем 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.


person Kurtis Nusbaum    schedule 11.04.2012    source источник


Ответы (17)


Если вы используете библиотеку поддержки 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);
    }
  }
}
person Arcao    schedule 04.04.2013
comment
+1, это правильный ответ. Обратите внимание, что onResumeFragments() не существует в классе Activity. Если вы используете базовый Activity, вместо него следует использовать onPostResume(). - person Alex Lockwood; 21.08.2013
comment
Прежде чем внедрять это решение, прочитайте это, чтобы понять, почему это взлом. В комментариях к другому решению этого вопроса скрыто гораздо более простое решение. - person twig; 25.05.2015
comment
Вызов super.onActivityResult не предотвращает исключение IllegalStateException, поэтому не является исправлением subj. проблема - person demaksee; 19.10.2015
comment
Этот вопрос - первое попадание в Google по этой проблеме, но принятый ответ, на мой взгляд, не самый лучший. Вместо этого следует принять этот ответ: stackoverflow.com/a/30429551/1226020 - person JHH; 16.03.2017

РЕДАКТИРОВАТЬ: Не ошибка, а скорее недостаток в структуре фрагментов. Лучшим ответом на этот вопрос является ответ, предоставленный @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 «уходит»), иначе этот фрагмент диалога может попытаться вызвать функции для зарегистрированного слушателя, когда этот слушатель больше не существует.

person Kurtis Nusbaum    schedule 11.04.2012
comment
решение, которое не требует копирования источника ... просто переопределите show() и поймайте IllegalStateException. - person Jeffrey Blattman; 05.11.2012
comment
Как изменить источник DialogFragment? Или вы можете опубликовать свое решение, упомянутое в конце вашего сообщения? - person Piotr Ślesarew; 23.11.2012
comment
@PeterSlesarew Я опубликовал свое (довольно конкретное) решение. - person Kurtis Nusbaum; 09.02.2013
comment
@KurtisNusbaum, вы предлагаете редактировать класс DialogFragment из библиотеки поддержки? - person Robertas Setkus; 05.07.2013
comment
Гах, это не ошибка! Платформа Android намеренно выбрасывает исключение, поскольку выполнение фрагментных транзакций внутри onActivityResult() небезопасно! Попробуйте это решение вместо этого: / - person Alex Lockwood; 21.08.2013
comment
@AlexLockwood Документация не предупреждала об этом, когда задавался этот вопрос. Кроме того, несмотря на то, что ваше решение выглядит хорошо сейчас, оно не работало в апреле 2012 года. onPostResume и onResumeFragments — относительно новые дополнения к библиотеке поддержки. - person hrnt; 08.01.2014
comment
(извините, onResumeFragments — это новое дополнение, а onPostResume — нет) - person hrnt; 08.01.2014

Комментарий, оставленный @Natix, представляет собой быстрый однострочный текст, который некоторые люди могли удалить. .

Самое простое решение этой проблемы — вызвать super.onActivityResult() ПЕРЕД запуском собственного кода. Это работает независимо от того, используете ли вы библиотеку поддержки или нет, и поддерживает согласованность поведения в вашей деятельности.

Есть:

Чем больше я читаю об этом, тем больше безумных хаков я видел.

Если вы все еще сталкиваетесь с проблемами, то вам следует проверить тот, который создал Алекс Локвуд.

person twig    schedule 24.05.2015
comment
Что произойдет, если у вас есть наследство? Вызов super.onActivityResult() может быть проблемой, если вы хотите сначала запустить свой код перед вызовом super, суперкласс может иметь свой собственный код внутри onActivityResult, будьте осторожны. - person Ricard; 13.06.2019
comment
Я добавляю super.onActivityResult(requestCode, resultCode, data) перед любым кодом, это исправило мою проблему. Но при добавлении наследования или переопределении onActivityResult по умолчанию мы должны обрабатывать onStart/onResume вручную. - person mochadwi; 31.03.2020

Я считаю, что это ошибка Android. В основном Android вызывает onActivityResult в неправильной точке жизненного цикла активности/фрагмента (до onStart()).

Об ошибке сообщается по адресу https://issuetracker.google.com/issues/36929762.

Я решил это, в основном сохранив Intent как параметр, который я позже обработал в onResume().

[EDIT] В настоящее время есть лучшие решения для этой проблемы, которых не было в 2012 году. См. другие ответы.

person hrnt    schedule 11.04.2012
comment
На самом деле это не совсем баг. Как указано в комментариях, в нем четко указано, что onActivityResult() вызывается перед onResume() - person Kurtis Nusbaum; 12.04.2012
comment
Вы читали последний комментарий к багу? Ошибка в том, что onActivityResult() вызывается перед onStart(), а не перед onResume(). - person hrnt; 12.04.2012
comment
Ах да, это тоже верно. Пропустил это. Хотя я все еще считаю, что другой отчет об ошибке немного более актуален для моей проблемы. - person Kurtis Nusbaum; 12.04.2012
comment
Он четко определен при вызове onActivityResult. Следовательно, это не может быть ошибкой, даже если в некоторых случаях это может показаться неуместным. - person sstn; 02.11.2012
comment
@sstn, не могли бы вы уточнить? Это четко определено, когда вызывается onActivityResult (= непосредственно перед onResume). Android не вызывает onActivityResult непосредственно перед onResume. Таким образом, это ошибка. - person hrnt; 15.01.2013

РЕДАКТИРОВАТЬ: Еще один вариант другой и, возможно, лучший (или, по крайней мере, то, что ожидает библиотека поддержки...)

Если вы используете 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
}

Это кажется мне более чистым и менее хакерским.

person Simon Jacobs    schedule 29.07.2013
comment
Использование обработчика для решения этой проблемы только добавляет задержку, поэтому более маловероятно возникновение проблемы. Но это не гарантирует, что проблема исчезнет! Это немного похоже на решение условий гонки с помощью Thread#sleep(). - person Alex Lockwood; 21.08.2013
comment
Вызов super.onActivityResult() — это самое простое рабочее решение, и, вероятно, это должен быть принятый ответ! Я случайно заметил отсутствующий супервызов и был приятно удивлен, что его добавление сработало. Это позволило мне удалить один из старых хаков, упомянутых на этой странице (сохранение диалога во временную переменную и отображение его в onResume()). - person Natix; 07.11.2013
comment
Хорошее решение. Единственная проблема заключается в том, что onActivityResult() не возвращает никакого значения, указывающего, обработали ли фрагменты результат. - person Michael; 22.04.2015
comment
Вызов super.onActivityResult() не устраняет сбой IllegalStateException в моем проекте - person demaksee; 19.10.2015

Существует два метода show() DialogFragment — 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 - в документации говорится: «Как и commit (), но позволяет выполнить фиксацию после сохранения состояния активности. Это опасно, потому что фиксация может быть потеряна, если активность необходимо позже восстановить из ее состояния. , поэтому его следует использовать только в тех случаях, когда состояние пользовательского интерфейса может неожиданно измениться для пользователя».

person gkee    schedule 25.04.2013

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

person handrenliang    schedule 24.05.2013

Я придумал третье решение, частично основанное на решении 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");
}
person PearsonArtPhoto    schedule 18.12.2012

onActivityResult() выполняется до onResume(). Вам нужно сделать свой пользовательский интерфейс в onResume() или позже.

Используйте логическое значение или что-то еще, что вам нужно, чтобы сообщить, что результат вернулся между обоими этими методами.

... Вот и все. Простой.

person Eurig Jones    schedule 29.07.2013

Я знаю, что на это ответили довольно давно... но есть гораздо более простой способ сделать это, чем некоторые другие ответы, которые я видел здесь... В моем конкретном случае мне нужно было показать DialogFragment из фрагментов onActivityResult() метод.

Это мой код для обработки этого, и он прекрасно работает:

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

Как упоминалось в некоторых других сообщениях, фиксация с потерей состояния может вызвать проблемы, если вы не будете осторожны... в моем случае я просто показывал пользователю сообщение об ошибке с помощью кнопки для закрытия диалогового окна, поэтому, если состояние, которое потеряно, это не имеет большого значения.

Надеюсь это поможет...

person Justin    schedule 13.06.2013

Это старый вопрос, но я решил его самым простым способом:

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

Это происходит потому, что когда вызывается #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;
    }
}
person Ricard    schedule 31.08.2017

Самое чистое решение, которое я нашел, это:

@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.
}
person fhucho    schedule 03.11.2013

Я получил эту ошибку при выполнении .show(getSupportFragmentManager(), "MyDialog"); в действии.

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

Если все еще не работает, этот пост (Show DialogFragment from onActivityResult) поможет мне решить проблему.

person Youngjae    schedule 25.02.2015

Другой путь:

@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
}
person Maher Abuthraa    schedule 22.06.2017

просто вызовите super.onActivityResult(requestCode, resultCode, data); перед обработкой фрагмента

person Thomas Klammer    schedule 20.07.2017

Как вы все знаете, эта проблема связана с тем, что onActivityResult() вызывается перед onstart(), поэтому просто вызовите onstart() при запуске в onActivityResult(), как я сделал в этом коде.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      onStart();
      //write you code here
}
person Bunny Bandewar    schedule 08.04.2015
comment
Вы не должны никогда напрямую вызывать методы жизненного цикла Android. Они должны вызываться только системой. - person Chantell Osejo; 28.09.2017