react-native-screens: (Android - Crash): FragmentManager is already executing transactions

Description

When react native screens are enabled on Android and the architecture is based on a single Activity plus React native is loaded in a Fragment, there’s a crash when navigating and restoring the fragment state.

Having a single Activity app and using Fragments with navigation components as describe here brings us to load React Native apps on a Fragment. We also want to enable React native screens, however, when enabled on Android, there’s the following crash: java.lang.RuntimeException: Unable to resume activity {com.swmansion.rnscreens.example/com.swmansion.rnscreens.example.MainActivity}: java.lang.IllegalStateException: FragmentManager is already executing transactions at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4445) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4477) at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7697) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952) Caused by: java.lang.IllegalStateException: FragmentManager is already executing transactions at androidx.fragment.app.FragmentManager.ensureExecReady(FragmentManager.java:1686) at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1716) at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:323) at com.swmansion.rnscreens.ScreenContainer.removeMyFragments(ScreenContainer.kt:216) at com.swmansion.rnscreens.ScreenContainer.onDetachedFromWindow(ScreenContainer.kt:228) at android.view.View.dispatchDetachedFromWindow(View.java:20538) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3942) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3934) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3934) at android.view.ViewGroup.removeViewInternal(ViewGroup.java:5560) at android.view.ViewGroup.removeViewInternal(ViewGroup.java:5531) at android.view.ViewGroup.removeView(ViewGroup.java:5462) at androidx.fragment.app.FragmentContainerView.removeView(FragmentContainerView.kt:288) at androidx.fragment.app.SpecialEffectsController$Operation$State.applyState(SpecialEffectsController.java:470) at androidx.fragment.app.DefaultSpecialEffectsController.applyContainerChanges(DefaultSpecialEffectsController.java:822) at androidx.fragment.app.DefaultSpecialEffectsController$1.run(DefaultSpecialEffectsController.java:111) at androidx.fragment.app.SpecialEffectsController$Operation.complete(SpecialEffectsController.java:713) at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:770) at androidx.fragment.app.SpecialEffectsController$Operation.completeSpecialEffect(SpecialEffectsController.java:690) at androidx.fragment.app.DefaultSpecialEffectsController$SpecialEffectsInfo.completeSpecialEffect(DefaultSpecialEffectsController.java:855) at androidx.fragment.app.DefaultSpecialEffectsController.startAnimations(DefaultSpecialEffectsController.java:154) at androidx.fragment.app.DefaultSpecialEffectsController.executeOperations(DefaultSpecialEffectsController.java:123) at androidx.fragment.app.SpecialEffectsController.executePendingOperations(SpecialEffectsController.java:306) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1903) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1814) at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1751) at androidx.fragment.app.Fragment.performResume(Fragment.java:3177) at androidx.fragment.app.FragmentStateManager.resume(FragmentStateManager.java:606) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:285) 2022-07-03 22:00:08.008 30152-30152/com.swmansion.rnscreens.example E/AndroidRuntime: at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968) at androidx.fragment.app.FragmentManager.dispatchResume(FragmentManager.java:2900) at androidx.fragment.app.FragmentController.dispatchResume(FragmentController.java:285) at androidx.fragment.app.FragmentActivity.onResumeFragments(FragmentActivity.java:334) at androidx.fragment.app.FragmentActivity.onPostResume(FragmentActivity.java:323) at androidx.appcompat.app.AppCompatActivity.onPostResume(AppCompatActivity.java:204) at android.app.Activity.performResume(Activity.java:8160) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4435)

The reason of the crash seems to be:

  • When setting up ScreenContainer it receives the FragmentManager from the Activity at some point.
  • This same FragmentManager is used by the Navigation Components to navigate between fragments.
  • When the Fragment that has react native tries to recover the state after a navigation, it tries to use the Activity FragmentManager which is already in use.

Screenshots

Steps To Reproduce

To reproduce, I’ve recreated in the Example app a common use case: Load React native, put the app in the background and trigger a deep link. I’ve prepared the Example to do this so the steps are:

  1. Launch the Example app
  2. Tap Stack Presentation
  3. Tap Form Sheet
  4. Put some values in the form.
  5. Background the app
  6. Launch the following adb comand: adb shell am start -W -a android.intent.action.VIEW -d "screens://screens" com.swmansion.rnscreens.example

Expected behavior

The react native screen should display without a crash

Actual behavior

Crash with the above stack trace

Reproduction

To reproduce the crash I’ve forked react-native-screens github project and modified the example with the described architecture. The project can be found here: https://github.com/luislukas/react-native-screens

I reported this issue originally on https://github.com/software-mansion/react-native-screens/issues/819 but due to our architecture we had to disable react-native-screens. We would like to enable them now so any hint/help would be appreciated.

Platform

  • iOS
  • Android
  • Web
  • Windows
  • tvOS

Architecture

I’ve forked the react-native-screens and added the Navigation Componentes plus Fragments architecture described aboved.

  • Paper
  • Fabric

Workflow

  • Managed workflow
  • Bare workflow

Package versions

I’m using a fork of this project just changing the Android project architecture.

package version
react-native
@react-navigation/native
@react-navigation/native-stack
react-native-screens
react-native-safe-area-context
react-native-gesture-handler
react-native-reanimated
expo

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 34 (7 by maintainers)

Most upvoted comments

Having same issue for a while now. App is pure react-native.

"@react-navigation/native": "^6.1.9", "react-native-screens": "3.29.0",

My project is open source and here is the navigation: https://github.com/lrtlt/mobile/blob/master/app/navigation/MainStack.tsx

Our whole app is React Native, not brownfield, and we don’t have any custom UI elements (except for what our dependencies may be doing, of course - nothing we wrote ourselves, at least). While it’s a fairly large app, we use a pretty standard setup, nothing custom.

Our navigation structure looks like this:

  • Stack
    • HOME_SCREEN: Bottom Tab
      • DASHBOARD_SCREEN
    • CAMPUS_EXERCISE_SCREEN: Stack
      • CAMPUS_VIDEO_EXERCISE_SCREEN: Material Top Tab
        • CAMPUS_VIDEO_EXERCISE_MAIN_SCREEN

The crash happens when we RESET the navigation from the CAMPUS_VIDEO_EXERCISE_MAIN_SCREEN to the DASHBOARD_SCREEN. The CAMPUS_VIDEO_EXERCISE_MAIN_SCREEN is fairly simple: it basically renders a WebView and some buttons The DASHBOARD_SCREEN is a lot more complex, with scrollviews in both directions, images, potentially a Modal, etc.

Main dependencies:

  • react-native: 0.70.13
  • expo: 47.0.14 (bare workflow)
  • react-native-screens: 3.22.0
  • @react-navigation/native: 6.1.8
  • @react-navigation/stack: 6.3.18
  • @react-navigation/bottom-tabs: 6.5.9
  • @react-navigation/material-top-tabs: 6.6.4
  • react-native-reanimated: 2.13.0
  • react-native-webview: 13.2.3

Also note this issue started happening after upgrading react-navigation from v4 to v6, which also updated react-native-screens from 2.16.1 to 3.22.0

@kkafar I’m unable to reproduce the issue and don’t know where it is happening since this issue is happening in production. Please find the crash log below

Caused by java.lang.IllegalStateException: FragmentManager is already executing transactions at androidx.fragment.app.FragmentManager.ensureExecReady(FragmentManager.java:1931) at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1967) at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:311) at com.swmansion.rnscreens.ScreenContainer.onUpdate(ScreenContainer.kt:366) at com.swmansion.rnscreens.ScreenContainer.performUpdates(ScreenContainer.kt:311) at com.swmansion.rnscreens.ScreenContainer.performUpdatesNow(ScreenContainer.kt:303) at com.swmansion.rnscreens.ScreenContainer.notifyChildUpdate(ScreenContainer.kt:87) at com.swmansion.rnscreens.Screen.setActivityState(Screen.kt:132) at com.swmansion.rnscreens.ScreenViewManager.setActivityState(ScreenViewManager.kt:49) at com.swmansion.rnscreens.ScreenViewManager.setActivityState(ScreenViewManager.kt:34) at com.swmansion.rnscreens.ScreenViewManager.setActivityState(ScreenViewManager.kt:21) at com.facebook.react.viewmanagers.RNSScreenManagerDelegate.setProperty(RNSScreenManagerDelegate.java:81) at com.facebook.react.uimanager.ViewManagerPropertyUpdater.updateProps(ViewManagerPropertyUpdater.java:46) at com.facebook.react.uimanager.ViewManager.updateProperties(ViewManager.java:84) at com.facebook.react.uimanager.NativeViewHierarchyManager.updateProperties(NativeViewHierarchyManager.java:142) at com.facebook.react.uimanager.UIImplementation.synchronouslyUpdateViewOnUIThread(UIImplementation.java:306) at com.facebook.react.uimanager.UIManagerModule.synchronouslyUpdateViewOnUIThread(UIManagerModule.java:402) at com.facebook.react.animated.PropsAnimatedNode.updateView(PropsAnimatedNode.java:118) at com.facebook.react.animated.NativeAnimatedNodesManager.updateNodes(NativeAnimatedNodesManager.java:747) at com.facebook.react.animated.NativeAnimatedNodesManager.handleEvent(NativeAnimatedNodesManager.java:600) at com.facebook.react.animated.NativeAnimatedNodesManager.onEventDispatch(NativeAnimatedNodesManager.java:562) at com.facebook.react.uimanager.events.EventDispatcherImpl.dispatchEvent(EventDispatcherImpl.java:116) at com.reactnativepagerview.PagerViewViewManager$createViewInstance$1$1.onPageScrolled(PagerViewViewManager.kt:43) at androidx.viewpager2.widget.CompositeOnPageChangeCallback.onPageScrolled(CompositeOnPageChangeCallback.java:59) at androidx.viewpager2.widget.CompositeOnPageChangeCallback.onPageScrolled(CompositeOnPageChangeCallback.java:59) at androidx.viewpager2.widget.ScrollEventAdapter.dispatchScrolled(ScrollEventAdapter.java:438) at androidx.viewpager2.widget.ScrollEventAdapter.onScrollStateChanged(ScrollEventAdapter.java:134) at androidx.recyclerview.widget.RecyclerView.dispatchOnScrollStateChanged(RecyclerView.java:5210) at androidx.recyclerview.widget.RecyclerView.setScrollState(RecyclerView.java:1550) at androidx.recyclerview.widget.RecyclerView.stopScroll(RecyclerView.java:2493) at androidx.recyclerview.widget.RecyclerView.onDetachedFromWindow(RecyclerView.java:2998) at android.view.View.dispatchDetachedFromWindow(View.java:14598) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3074) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3066) at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4606) at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4579) at android.view.ViewGroup.removeView(ViewGroup.java:4510) at com.swmansion.rnscreens.ScreenContainer.removeView(ScreenContainer.kt:66) at androidx.fragment.app.SpecialEffectsController$Operation$State.applyState(SpecialEffectsController.java:452) at androidx.fragment.app.DefaultSpecialEffectsController.applyContainerChanges(DefaultSpecialEffectsController.java:743) at androidx.fragment.app.DefaultSpecialEffectsController$1.run(DefaultSpecialEffectsController.java:108) at androidx.fragment.app.SpecialEffectsController$Operation.complete(SpecialEffectsController.java:695) at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:744) at androidx.fragment.app.SpecialEffectsController$Operation.completeSpecialEffect(SpecialEffectsController.java:672) at androidx.fragment.app.DefaultSpecialEffectsController$SpecialEffectsInfo.completeSpecialEffect(DefaultSpecialEffectsController.java:776) at androidx.fragment.app.DefaultSpecialEffectsController.startAnimations(DefaultSpecialEffectsController.java:147) at androidx.fragment.app.DefaultSpecialEffectsController.executeOperations(DefaultSpecialEffectsController.java:120) at androidx.fragment.app.SpecialEffectsController.executePendingOperations(SpecialEffectsController.java:297) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2202) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106) at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1971) at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:311) at com.swmansion.rnscreens.ScreenContainer.onUpdate(ScreenContainer.kt:366) at com.swmansion.rnscreens.ScreenContainer.performUpdates(ScreenContainer.kt:311) at com.swmansion.rnscreens.ScreenContainer.performUpdatesNow(ScreenContainer.kt:303) at com.swmansion.rnscreens.ScreenContainer.notifyChildUpdate(ScreenContainer.kt:87) at com.swmansion.rnscreens.Screen.setActivityState(Screen.kt:132) at com.swmansion.rnscreens.ScreenViewManager.setActivityState(ScreenViewManager.kt:47) at com.swmansion.rnscreens.ScreenViewManager.setActivityState(ScreenViewManager.kt:34) at com.swmansion.rnscreens.ScreenViewManager.setActivityState(ScreenViewManager.kt:21) at com.facebook.react.viewmanagers.RNSScreenManagerDelegate.setProperty(RNSScreenManagerDelegate.java:81) at com.facebook.react.uimanager.ViewManagerPropertyUpdater.updateProps(ViewManagerPropertyUpdater.java:46) at com.facebook.react.uimanager.ViewManager.updateProperties(ViewManager.java:84) at com.facebook.react.uimanager.NativeViewHierarchyManager.updateProperties(NativeViewHierarchyManager.java:142) at com.facebook.react.uimanager.UIImplementation.synchronouslyUpdateViewOnUIThread(UIImplementation.java:306) at com.facebook.react.uimanager.UIManagerModule.synchronouslyUpdateViewOnUIThread(UIManagerModule.java:402) at com.facebook.react.animated.PropsAnimatedNode.updateView(PropsAnimatedNode.java:118) at com.facebook.react.animated.NativeAnimatedNodesManager.updateNodes(NativeAnimatedNodesManager.java:747) at com.facebook.react.animated.NativeAnimatedNodesManager.runUpdates(NativeAnimatedNodesManager.java:641) at com.facebook.react.animated.NativeAnimatedModule$1.doFrameGuarded(NativeAnimatedModule.java:263) at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:29) at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:175) at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:85) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858) at android.view.Choreographer.doCallbacks(Choreographer.java:672) at android.view.Choreographer.doFrame(Choreographer.java:605) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:846) at android.os.Handler.handleCallback(Handler.java:742) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:157) at android.app.ActivityThread.main(ActivityThread.java:5603) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:774) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:652)

Hi @nvu @luislukas! I dived into it and came up with (I hope) proper solution: https://github.com/software-mansion/react-native-screens/pull/1553

Would you mind checking these changes out and letting me know whether it works for you?

You can put following line in your package.json

"react-native-screens": "software-mansion/react-native-screens#@kkafar/fix-1506