[Android] ネストしたFragmentのonActivityResultが呼ばれない件

このエントリーをはてなブックマークに追加

現象

Activity > Fragment > ViewPager > Fragment  

という構成にするとViewPager内のFragmentonActivityResultが呼ばれなくなってしまう。
また、Activity直下にFragmentが複数あると、想定していないFragmentonActivityResultが呼ばれてしまう。

例:

Activity > (DrawerFragment + (ParentFragment > ViewPager > ChildFragment))  

↑このような構成の時、ViewPager内のChildFragmentからstartActivityForResultを呼ぶと、ChildFragmentonActivityResultが呼ばれないだけでなく、DrawerFragmentonActivityResultが呼ばれてしまったりする。
なお、ParentFragmentonActivityResultも呼ばれない。

原因

どのFragmentからActivityが呼ばれたかは、そのFragmentが属するFragmentManager内のIndexで管理しているようだ。

ネストされたFragmentからActivityを呼ぶと、FragmentのIndexは親FragmentFragmentManager(getChildFragmentManagerで取得するFragmentManager)から取得される。

しかし、Activity#onActivityResultで呼び出し元のFragmentを判定する時は、ActivityFragmentManagerから対応するFragmentを取得しようとするようである。そのため、見当違いのFragmentonActivityResultが呼ばれてしまう。

なお、サポートライブラリではそもそも親Fragmentから子FragmentonActivityResultを呼ぶ機能はない模様。

対策

よくわからないFragmentonActivityResultが呼ばれてしまう件は、とりあえず無視。そのFragmentonActivityResultを実装していなければ問題ないし、実装している場合もきちんと条件分岐していれば問題ない。

FragmentonActivityResultに関しては、下記のようにして対応する

  1. Fragmentで結果を受け取りたい場合は、親FragmentstartActivityForResultを呼ぶ。
  2. FragmentではonActivityResultを受け取れるので、親FragmentonActivityResult内で、すべての子FragmentonActivityResultを手動で呼ぶ
// BaseFragment.java
protected void callChildOnActivityResult(FragmentManager fm,  
    int requestCode, int resultCode, Intent data) {

    List<Fragment> fragments = fm.getFragments();
    if (fragments != null && fragments.size() > 0) {
        for (Fragment fragment : fragments) {
            fragment.onActivityResult(requestCode, resultCode, data);            
        }
    }

}

上記のようなメソッドを用意し、必要な箇所(親FragmentonActivityResult)で呼ぶとよさげ。

// FooActivity.java
public static void startActivity(Fragment fragment) {  
    Intent intent = new Intent(fragment.getActivity(), FooActivity.class);

    // 親Fragmentがあれば親Fragmentから呼ぶ
    Fragment parentFragment = fragment.getParentFragment();
    if (parentFragment == null) {
        fragment.startActivityForResult(intent, "fragment_content");
    } else {
        parentFragment.startActivityForResult(intent, "fragment_content");
    }
}

Activityを開始するときはこんな感じ。