From cbc86bb6d889b80fa083a63ea2ec88d5acb6c637 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 27 Nov 2019 22:00:16 +0100 Subject: [PATCH] Avoid changing back stack when fragment manager is not resumed. (#237) This change fixes the problem when there are changes being made to the stack while the hosting activity is in paused state (e.g. an activity-based dialog from google play services). In such a case it is not allowed to do any changes which can affect fragment manager's state (e.g. updating backstack). For other changes we use `commitAllowingStatLoss` to indicate we are not interested in fragment manager handling their restoration and hence we can perform most operations. Unfortunately installing back handler is not allowed without state change and we need to wait until the fragment host is resumed before we install it. --- .../com/swmansion/rnscreens/ScreenStack.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.java b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.java index 52061574..2511315e 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.java +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.java @@ -2,10 +2,10 @@ package com.swmansion.rnscreens; import android.content.Context; import android.view.View; -import android.view.WindowInsets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -34,6 +34,15 @@ public class ScreenStack extends ScreenContainer { } }; + private final FragmentManager.FragmentLifecycleCallbacks mLifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks() { + @Override + public void onFragmentResumed(FragmentManager fm, Fragment f) { + if (mTopScreen == f) { + setupBackHandlerIfNeeded(mTopScreen); + } + } + }; + public ScreenStack(Context context) { super(context); @@ -93,12 +102,13 @@ public class ScreenStack extends ScreenContainer { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); FragmentManager fm = getFragmentManager(); + fm.removeOnBackStackChangedListener(mBackStackListener); + getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks); if (!fm.isStateSaved()) { // state save means that the container where fragment manager was installed has been unmounted. // This could happen as a result of dismissing nested stack. In such a case we don't need to // reset back stack as it'd result in a crash caused by the fact the fragment manager is no // longer attached. - fm.removeOnBackStackChangedListener(mBackStackListener); fm.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } } @@ -106,9 +116,7 @@ public class ScreenStack extends ScreenContainer { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (mTopScreen != null) { - setupBackHandlerIfNeeded(mTopScreen); - } + getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false); } @Override @@ -238,6 +246,12 @@ public class ScreenStack extends ScreenContainer { * that case we want the parent navigator or activity handler to take over. */ private void setupBackHandlerIfNeeded(ScreenStackFragment topScreen) { + if (!mTopScreen.isResumed()) { + // if the top fragment is not in a resumed state, adding back stack transaction would throw. + // In such a case we skip installing back handler and use FragmentLifecycleCallbacks to get + // notified when it gets resumed so that we can install the handler. + return; + } getFragmentManager().removeOnBackStackChangedListener(mBackStackListener); getFragmentManager().popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); ScreenStackFragment firstScreen = null;