I have a viewpager2 with multiple fragments in FragmentStateAdapter. Whenever I try to open a new fragment and then go back to my current one with viewpager2, I get an exception
This adapter/view is useful as a replacement for FragmentStatePagerAdapter.
If what you seek is to preserve the Fragments on re-entrance from the Backstack that would be extremely difficult to achieve with this adapter.
The team placed to many breaks in place to prevent this, only god knows why...
They could have used a self detaching lifeCycle observer, which ability was already foresaw in its code, but nowhere in the android architecture makes use of that ability....
They should have used this unfinished component to listen to the global Fragments lifecycle instead of its viewLifeCycle, from here on, one can scale the listening from the Fragment to the viewLifeCycle. (attach/detach viewLifeCycle observer ON_START/ON_STOP)
Second... even if this is done, the fact that the viewPager itself is built on top of a recyclerView makes it extremely difficult to handle what you would expect from a Fragment's behavior, which is an state of preservation, a one time instantiation, and a well defined lifecycle (controllable/expected destruction).
This adapter is contradictory in its functionality, it checks if the viewPager has already been fed with Fragments, while still requiring a "fresh" adapter on reentrance.
It preserves Fragments on exit to the backStack, while expecting to recreate all of them on reentrance.
The breaks on place to prevent a field instantiated adapter, assuming all other variables are already accounted for a proper viewLifeCycle handling (registering/unregistering & setting and resetting of parameters) are:
@Override
public final void restoreState(@NonNull Parcelable savedState) {
if (!mSavedStates.isEmpty() || !mFragments.isEmpty()) {
throw new IllegalStateException(
"Expected the adapter to be 'fresh' while restoring state.");
}
.....
}
Second break:
@CallSuper
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
checkArgument(mFragmentMaxLifecycleEnforcer == null);
mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
mFragmentMaxLifecycleEnforcer.register(recyclerView);
}
where mFragmentMaxLifecycleEnforcer must be == null on reentrance or it throws an exception in the checkArgument().
Third: A Fragment garbage collector put in place upon reentrance (to the view, from the backstack) that is postDelayed at 10 seconds that attempts to Destroy off screen Fragments, causing memory leaks on all offscreen pages because it kills their respective FragmentManagers that controls their LifeCycle.
private void scheduleGracePeriodEnd() {
final Handler handler = new Handler(Looper.getMainLooper());
final Runnable runnable = new Runnable() {
@Override
public void run() {
mIsInGracePeriod = false;
gcFragments(); // good opportunity to GC
}
};
mLifecycle.addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
handler.removeCallbacks(runnable);
source.getLifecycle().removeObserver(this);
}
}
});
handler.postDelayed(runnable, GRACE_WINDOW_TIME_MS);
}
And all of them because of its main culprit: the constructor:
public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Lifecycle lifecycle) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}