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)
1234567public
void
show(FragmentManager manager, String tag) {
mDismissed =
false
;
mShownByMe =
true
;
FragmentTransaction ft = manager.beginTransaction();
ft.add(
this
, tag);
ft.commit();
}
そこで、DialogFragment
の子クラスで以下のようにshow()
を Override します
1234567891011121314151617181920212223242526public
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 を表示するようにします。
123456789101112131415161718192021222324252627282930public
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日現在でもこの問題は存在するようです。