Quick note: I did my due diligence before asking the question: I know that this sounds like a dupe. But none of the existing questions ask the exact question I have, and none of the solutions on the existing questions work for me. Thanks.
TL;DR: When restoring a fragment that has a nested fragment, the childFragmentManager
adds the nested fragment back, but does not restore its state. How do I make it do so?
I have a ViewPager
that is backed by a FragmentStatePagerAdapter
. The FSPA
returns fragments, each of which in turn have one other fragment added when setUserVisibleHint
is true
. I'm basically avoiding the cost of loading one offscreen fragment which is the default ViewPager
behaviour. The inner fragment is a list of items, so maintaining scroll position is important.
Here's the sequence of events:
- Open page
N
, because it's visible, the inner fragment is added - Go to page
N+2
orN-2
: now the fragment at positionN
is destroyed because it's past theViewPager
'soffScreenLimit
of 1 - Then when I come back to
N
, the fragment manager conveniently added the inner fragment back (because ofsavedInstanceState
I'm guessing) - BUT: and here's the bug: the newly added child fragment's state is not saved or restored: the fragment is freshly created
This leads to a terrible user experience because when coming back to position N
, the list is scrolled all the way back to top.
Is there any way to avoid this? I have tried:
- manually saving the state using
getChildFragmentManager().saveFragmentInstanceState(myInnerFragment)
- but I don't know where or how to restore it because the child fragment manager added the fragment back for me
- retaining the instance variable
mInnerFragment
and replacing the automatically added fragment with this- this is wrong for a lot of reasons, but it also looks bad because we're replacing the already added fragment with this one so there is a visible jumping when swiping back to position
N
- this is wrong for a lot of reasons, but it also looks bad because we're replacing the already added fragment with this one so there is a visible jumping when swiping back to position
Note that I can't use setRetainInstanceState(true)
in any of the fragments: outer or inner, because all this is nested in another fragment. A lot of the answers have suggested using reflection to hang on to and restore the mChildFragmentManager
, but that seems risky, and in any case I can't use that because of the setRetainInstance
restriction.
Code
OuterFragment.java
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!isVisibleToUser) {
return;
}
addInnerFragment();
}
private void addInnerFragment() {
if (getInnerFragment() != null && getInnerFragment().isAdded()) {
return;
}
FragmentTransaction transaction =
getChildFragmentManager().beginTransaction();
transaction.replace(
R.id.fragment_container,
getInnerFragment(),
FRAGMENT_TAG);
transaction.commit();
}
private Fragment getInnerFragment() {
if (mInnerFragment != null) {
return mInnerFragment;
}
// The fragment could have already been added if we're coming back from a savedInstanceState.
Fragment fragment =
getChildFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment != null) {
// Verified using debugger that this condition is being hit when I come
// back to `N`
mInnerFragment = fragment;
return mInnerFragment;
}
mInnerFragment = InnerFragment.newInstance();
return mInnerFragment;
}