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

現象 ​

Activity > Fragment > ViewPager > Fragment

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

例:

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

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

原因 ​

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

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

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

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

対策 ​

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

子FragmentのonActivityResultに関しては、下記のようにして対応する

  1. 子Fragmentで結果を受け取りたい場合は、親FragmentのstartActivityForResultを呼ぶ。
  2. 親FragmentではonActivityResultを受け取れるので、親FragmentのonActivityResult内で、すべての子FragmentのonActivityResultを手動で呼ぶ
java
// 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);            
        }
    }

}

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

java
// 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を開始するときはこんな感じ。