問題点
主にバックスタックが存在するFragmentに対して、NestedFragment(Fragment in Fragment) 状態のFragmentからstartActivityForResult() をコールすると、onActivityResult() が呼ばれません。
※NestedFragmentについては以下
http://developer.android.com/intl/ja/about/versions/android-4.2.html#NestedFragments
Fragmentの構成
MainActivity - FirstFragment
|
+ SecondFragment - SecondChildFragment
上記のように
- MainActivityに FirstFragment、SecondFragment が存在する。
- FristFragmentはSecondFragmentのバックスタックとして存在する。
- SecondFragmentの一部の表示には、SecondChildFragmentが存在する。(Fragment in Fragment)
という状態の場合
SecondChildFragmentでstartActivityForResult()をコールすると
本来 SecondFragment に onActivityResult() がコールされる事を期待するのですが
実際は、FirstFragment の onActivityResult() がコールされます。
※本当は SecondChildFragment の onActivityResult() がコールされて欲しいのですが、NestedFragment では、されません。
#解決方法
これを解消するには、以下のように親のFragment(今回の場合はSecondFragment)のstartActivityForResult()をコールすればOKです。
@Override
public void startActivityForResult(Intent intent, int requestCode) {
Fragment parent = getParentFragment();
if (parent != null) {
parent.startActivityForResult(intent, requestCode);
} else {
super.startActivityForResult(intent, requestCode);
}
}
#原因
以下、少し長くなります。
興味のある方は読んでみてください。
##Fragment#startActivityForResult() の requestCode は16bit以下にする
startActivityForResult() は Activity でも Fragment でも使用できます。
FragmentからstartActivityForResult呼ばれた場合、onActivityResult()はFragmentへ通知されます。
(正確には親のActivityと、呼び出し元のFragmentの2箇所で呼ばれます)
これは、Activity側のonActivityResult() の内部実装で
requestCodeが16bit以上であれば、自身のActivityの他に、自身が管理しているFragmentへも
onActivityResult() を通知しているためです。
このため、Fragment#startActivityResult() では、内部的に requestCode の上位16bitへ値を設定しています。
これにより、使う側から指定する requestCode は、16bit以下にする必要があります。
詳しくはyanzmさんのサイトにあります。(いつもお世話になっているサイトです)
http://y-anz-m.blogspot.jp/2012/05/support-package-fragment.html
##requestCode の上位16bitには、Fragmentのインデックスが付与される
Fragment#startActivityForResult() の内部実装を見ると、親のActiivtyのstartActivityFromFragment()をコールしているようです。
ここソースコードを見ると、上位16bitにはFragmentのインデックス(をプラス1した値)を付与している事がわかります。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#666
super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
インデックスには、Fragmentが追加された(FragmentTransaction#add()された)順番の値が入るようです。
##Activity#onActivityResult() では、どの Fragment に対して通知を行う?
Activity#onActivityResult() の実装を見ると、上位16bitの値を取り出して
その位置(インデックス)の Fragment に対してFragment#onActivityResult() を通知していました。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#128
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
int index = requestCode>>16;
if (index != 0) {
index--;
if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) {
Log.w(TAG, "Activity result fragment index out of range: 0x"
+ Integer.toHexString(requestCode));
return;
}
Fragment frag = mFragments.mActive.get(index);
if (frag == null) {
Log.w(TAG, "Activity result no fragment exists for index: 0x"
+ Integer.toHexString(requestCode));
}
frag.onActivityResult(requestCode&0xffff, resultCode, data);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
先ほど startActivityForResult() で付与されたインデックスのFragmentに対して通知を行っています。
NestedFragmentのインデックスは0、親Fragmentのインデックスは1
NestedFragmentである SecondChildFragment には、バックスタックが存在しないのでインデックスは0になります。
親のFragmentである SecondFragment には、バックスタックが1つ存在するのでインデックスは1になります。
※あと、FirstFragmentのインデックスは0になります。
これにより、SecondChildFragment#startActivityForResult() をコールした場合、SecondChildFragmentのインデックスは0なので
- requestCodeの上位16bitのインデックスは0が付与される。(実際はインデックスをプラス1した値なので、1が入る)
- onActivityResult() では、上位16bitから取り出したインデックスは0となる。
- インデックス0は、FirstFragmentなので、FirstFragment#onActivityResult() がコールされる。
という現象が起こることになります。
##そもそも、NestedFragmentのonActivityResult()は呼ばれない!
NestedFragmentではonActivityResult()は呼ばれません。
これを解消するためには、親のFragmentから、NestedFragmentのonActivityResult()を明示的にコールしてやる必要があります。
(他にスマートなやり方があったら教えてください)
この実装をしていた時の問題点として上で説明したことのような現象が発生します。
private SecondChildFragment mChildFragment;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
.
.
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
mChildFragment = SecondChildFragment.newInstance();
transaction
.add(R.id.fragment_area, mChildFragment)
.commit();
.
.
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// NestedFragment は onActivityResult() が呼ばれないので、明示的にここから呼ぶ
mChildFragment.onActivityResult(requestCode, resultCode, data);
}
まとめ
NestedFragment での startActivityForResult() は、親FragmentのstartActivityForResult() を呼びましょう。