LoginSignup
41
29

More than 5 years have passed since last update.

FragmentがネストしているときのsetTargetFragment()は要注意

Last updated at Posted at 2014-12-26

Fragmentの処理結果通知先の取得

Fragmentの処理結果通知先を取得する方法を調べると、たいてい以下のメソッドが出てきます。

  1. Fragment#getActivity()
  2. Fragment#getTargetFragment()

もちろん、通常は問題ないですが、2のケースでtargetFragmentが親Fragmentの場合は問題があります。

何が問題か

Activity, Fragmentが再生成された場合、最初に設定したFragmentとは異なるFragmentが設定されています。具体的には以下のケース。

  • 親Fragment
    • 子Fragment1(targetFragment=親Fragment)
    • 子Fragment2(targetFragment=親Fragment)

この場合、再生成後はどちらのtargetFragmentも子Fragment1になります。

原因

AndroidがtargetFragmentをどう扱っているかというと、下記のようにonSaveInstanceState()のタイミングでFragmentManager内でputFragment()を呼び出して、保持しています。

FragmentManager#saveAllState()
...

if (f.mTarget != null) {
    if (f.mTarget.mIndex < 0) {
        throwException(new IllegalStateException(
                "Failure saving state: " + f
                + " has target not in fragment manager: " + f.mTarget));
    }
    if (fs.mSavedFragmentState == null) {
        fs.mSavedFragmentState = new Bundle();
    }
    putFragment(fs.mSavedFragmentState,
            FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
    if (f.mTargetRequestCode != 0) {
        fs.mSavedFragmentState.putInt(
                FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
                f.mTargetRequestCode);
    }
}

...

ちなみに、再生成時は以下のようにonAttach()前にgetFragment()で取得したFragmentを設定しています。

FragmentManager#moveToState()
...

switch (f.mState) {
    case Fragment.INITIALIZING:
        if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
        if (f.mSavedFragmentState != null) {
            f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
                    FragmentManagerImpl.VIEW_STATE_TAG);
            f.mTarget = getFragment(f.mSavedFragmentState,
                    FragmentManagerImpl.TARGET_STATE_TAG);
            if (f.mTarget != null) {
                f.mTargetRequestCode = f.mSavedFragmentState.getInt(
                        FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
            }
            f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
                    FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
            if (!f.mUserVisibleHint) {
                f.mDeferStart = true;
                if (newState > Fragment.STOPPED) {
                    newState = Fragment.STOPPED;
                }
            }
        }
        f.mActivity = mActivity;
        f.mParentFragment = mParent;
        f.mFragmentManager = mParent != null
                ? mParent.mChildFragmentManager : mActivity.mFragments;
        f.mCalled = false;
        f.onAttach(mActivity);

...

で、putFragment()を見てみると、以下のようにFragmentのmIndexを保持しているだけです。
このmIndexはそのFragmentManager内のFragmentのインデックス番号です。
なので、Fragmentがネストしているような、FragmentManagerが別オブジェクトの場合は
getFragment()で別のFragmentが返ってきてしまいます(ちなみにmIndexに該当するFragmentが存在しない場合はIllegalStateException)。

FragmentManagerImpl#putFragment()
...

@Override
public void putFragment(Bundle bundle, String key, Fragment fragment) {
    if (fragment.mIndex < 0) {
        throwException(new IllegalStateException("Fragment " + fragment
                + " is not currently in the FragmentManager"));
    }
    bundle.putInt(key, fragment.mIndex);
}

...

対策

親Fragmentの場合はgetParentFragment()ですかね。
通知元で通知先について何も知らない場合は以下の優先順位で通知先を探す感じでしょうか。

  1. Fragment#getTargetFragment()
  2. Fragment#getParentFragment()
  3. Fragment#getActivity()
41
29
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
29