Показать 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.

Куртис Нусбаум
источник

Ответы:

76

Если вы используете библиотеку поддержки 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

РЕДАКТИРОВАТЬ: Не ошибка, а скорее недостаток в структуре фрагментов. Лучший ответ на этот вопрос дает @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? Или вы можете опубликовать свое решение, упомянутое в конце вашего сообщения?
Петр lesarew
1
@PeterSlesarew Я опубликовал свое (довольно конкретное) решение.
Куртис Нусбаум
9
Ага, это не баг! Платформа Android намеренно генерирует исключение, потому что выполнять транзакции фрагментов внутри небезопасно onActivityResult()! Вместо этого попробуйте это решение: stackoverflow.com/questions/16265733/…
Alex Lockwood
2
@AlexLockwood Документация не предупреждала об этом, когда задавался этот вопрос. Вдобавок, хотя ваше решение сейчас выглядит хорошо , оно не работало еще в апреле 2012 года onPostResumeи onResumeFragmentsявляется относительно новым дополнением к библиотеке поддержки.
hrnt 08
24

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

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

Есть:

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

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

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

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

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

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

[РЕДАКТИРОВАТЬ] В настоящее время существуют лучшие решения этой проблемы, которые не были доступны еще в 2012 году. См. Другие ответы.

hrnt
источник
8
На самом деле это не совсем ошибка. Как указано в комментариях, в нем четко указано, что onActivityResult()это называлось раньшеonResume()
Куртис Нусбаум
2
Вы читали последний комментарий к ошибке? Ошибка в том, что onActivityResult () вызывается перед onStart (), а не в том, что он вызывается перед onResume ().
hrnt
Ах да, это тоже правда. Пропустил. Хотя я по-прежнему считаю, что другой отчет об ошибке немного больше относится к моей проблеме.
Куртис Нусбаум
Это четко определено, когда вызывается onActivityResult. Следовательно, это не может быть ошибкой, даже если в некоторых случаях может показаться несоответствующим.
sstn 02
1
@sstn, не могли бы вы уточнить? Он четко определен, когда вызывается onActivityResult (= непосредственно перед onResume). Android не вызывает onActivityResult непосредственно перед onResume. Таким образом, это ошибка.
hrnt
11

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

Если вы используете 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().
Алекс Локвуд,
27
Звонок super.onActivityResult()- это самое простое рабочее решение, и, вероятно, это должен быть принятый ответ! Я случайно заметил пропавший супер-вызов и был приятно удивлен, что его добавление просто сработало. Это позволило мне удалить один из старых приемов, упомянутых на этой странице (сохранение диалогового окна во временную переменную и его отображение onResume()).
Natix 06
Хорошее решение. Единственная проблема заключается в том, 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 - в документации указано: «Как и commit (), но позволяет выполнить фиксацию после сохранения состояния действия. Это опасно, потому что фиксация может быть потеряна, если действие необходимо позже восстановить из его состояния. , поэтому это следует использовать только в тех случаях, когда состояние пользовательского интерфейса может неожиданно измениться для пользователя ".

gkee
источник
4

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

Handrenliang
источник
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;
    }
}
Ricard
источник
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
}
Махер Абутраа
источник
0

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

Томас Кламмер
источник
-3

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

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