2014/08/17
onActivityResult 内で DialogFragment.show() を実行すると IllegalStateException が発生する
FragmentActivity.onActivityResult()
内で DialogFragment.show()
を実行すると、以下の様に IllegalStateException が発生して落ちてしまいます。Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
onActivityResult()
が呼ばれる時には Activity は復活しているはずなので、IllegalStateException はおかしいと思うのですが…調べてみると、Support Library v4 の既知のバグらしい。
確かに、
android.support.v4.app.DialogFragment
を android.app.DialogFragment
に変更すると問題なく動作しました。2.x 系のシェアは 15% 以下になってきたようなので、新規のアプリでは 2.x 系をサポートしないのが良いかもしれません。
とはいえ、既存のアプリは対応せざるを得ず、まだまだ Support Library を使う必要がありそうですが…
回避方法
回避方法を調べてみたのですが、大きく分けると次の 3通りに分類できそうです。- Support Library を使わない
新規で作成するのであれば、これが正攻法ですね。
既存のアプリでも、2.x 系のサポートを終わらせて、移行するのも良いかもしれません。
ただ、残念ながらそうもいかない場合も多いと思います。 DialogFragment.show()
を Override して使う
DialogFragment.show()
の中は以下のようになっています。
android.support.v4.app.DialogFragment (Support Library rev. 20)
public void show(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }
そこで、DialogFragment
の子クラスで以下のようにshow()
を Override します
public class TestDialog extends DialogFragment { // 省略 @Override public void show(FragmentManager manager, String tag) { // mDismissed = false; // mShownByMe = true; try { Field mDismissedField = DialogFragment.class.getDeclaredField("mDismissed"); mDismissedField.setAccessible(true); mDismissedField.set(this, true); Field mShownByMeField = DialogFragment.class.getDeclaredField("mShownByMe"); mShownByMeField.setAccessible(true); mShownByMeField.set(this, true); } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { throw new RuntimeException(e); } FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commitAllowingStateLoss(); } // 省略 }
commit()
をcommitAllowingStateLoss()
に書き換えたいだけなのですが、リフレクションを使わないといけないため、とても見にくくなってしまいました。
mDissmissed
とmShownByMe
を無視して書いてある例もありましたが、あまりお勧めはできません。
この方法、Dialog の状態を保存しておいて、表示の時に復活させようとすると失敗します。
また、Support Library を更新するたびに、DialogFragment.show()
の内容を確認して、修正があったら小クラスの方にも修正を加えなければなりません。
結構面倒くさいです。
onActivityResult()
内では Dialog を表示せず、onResumeFragments()
内で表示する
現時点で最も現実的な解はこれかもしれません。
onActivityResult()
内で Dialog を表示することを諦め、フラグを設定するだけにします。
そして、onResumeFragments()
内でフラグをチェックして Dialog を表示するようにします。
public class MainActivity extends FragmentActivity { private static final String TEST_DIALOG_TAG = "dialog"; private static final int NO_DIALOG = -1; private int mShowDialogNo = NO_DIALOG; // 省略 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ mShowDialogNo = requestCode; } @Override protected void onResumeFragments() { super.onResumeFragments(); switch (mShowDialogNo) { case 1: DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TEST_DIALOG_TAG); if (dialog == null) { dialog = new TestDialog(); } dialog.show(getSupportFragmentManager(), TEST_DIALOG_TAG); break; default: break; } mShowDialog = NO_DIALOG; } }
記述量は増えてしまいますが、仕方がありません。
補足
一部情報では Support Library rev. 18 で修正されたとあったのですが、rev. 20 で確認したところ、修正されている形跡はありませんでした。残念ながら、2014年8月16日現在でもこの問題は存在するようです。