Description
Our app received a crash report from Google Play Console on a release build using the new Gamma Tabs API (com.swmansion.rnscreens.gamma.tabs).
The crash is a java.lang.IllegalStateException: FragmentManager is already executing transactions, thrown from TabsContainer.updateSelectedFragment (TabsContainer.kt:563) when it calls commitNowAllowingStateLoss.
The root cause appears to be re-entrancy of the FragmentManager. The chain is:
FragmentManager is in the middle of dispatchResume / moveToState (an active fragment transaction is executing).
TabsScreenFragment.onResume (TabsScreenFragment.kt:28) fires emitOnDidAppear.
- That
onDidAppear event is dispatched synchronously to Reanimated (NodesManager.onEventDispatch → performOperationsRespectingDrawPass → FabricUIManager.scheduleMountItem), which synchronously flushes mount items while still inside the resume pass.
- The flushed mount items reach
TabsHost.didMountItems → TabsContainer.flushPendingUpdates → performContainerUpdate → performSelectedTabUpdate → updateSelectedFragment, which calls commitNowAllowingStateLoss.
- Because a fragment transaction is already executing (step 1),
ensureExecReady throws FragmentManager is already executing transactions.
So a synchronous tab/mount update is being committed re-entrantly from within Fragment.onResume, which the FragmentManager does not allow.
Expected: Pending tab updates committed during/triggered by a fragment lifecycle callback should be deferred (e.g. posted) rather than committed synchronously, so they never re-enter a FragmentManager that is already executing transactions.
Full stack trace
java.lang.RuntimeException
at android.app.ActivityThread.performResumeActivity (ActivityThread.java:6269)
at android.app.ActivityThread.handleResumeActivity (ActivityThread.java:6342)
at android.app.servertransaction.ResumeActivityItem.execute (ResumeActivityItem.java:73)
at android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:63)
at android.app.servertransaction.TransactionExecutor.executeLifecycleItem (TransactionExecutor.java:169)
at android.app.servertransaction.TransactionExecutor.executeTransactionItems (TransactionExecutor.java:101)
at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:80)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:3150)
at android.os.Handler.dispatchMessage (Handler.java:110)
at android.os.Looper.loopOnce (Looper.java:273)
at android.os.Looper.loop (Looper.java:363)
at android.app.ActivityThread.main (ActivityThread.java:10060)
at java.lang.reflect.Method.invoke
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:632)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:975)
Caused by java.lang.IllegalStateException: FragmentManager is already executing transactions
at androidx.fragment.app.FragmentManager.ensureExecReady (FragmentManager.java:1947)
at androidx.fragment.app.FragmentManager.execSingleAction (FragmentManager.java:1977)
at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss (BackStackRecord.java:323)
at com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.updateSelectedFragment (TabsContainer.kt:563)
at com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.onMenuItemSelected (TabsContainer.kt:602)
at com.google.android.material.navigation.NavigationBarView$1.onMenuItemSelected (NavigationBarView.java:449)
at androidx.appcompat.view.menu.MenuBuilder.dispatchMenuItemSelected (MenuBuilder.java:833)
at androidx.appcompat.view.menu.MenuItemImpl.invoke (MenuItemImpl.java:157)
at androidx.appcompat.view.menu.MenuBuilder.performItemAction (MenuBuilder.java:984)
at com.google.android.material.navigation.NavigationBarView.setSelectedItemId (NavigationBarView.java:1037)
at com.swmansion.rnscreens.gamma.tabs.container.CustomBottomNavigationView.setSelectedItemId (CustomBottomNavigationView.kt:32)
at com.swmansion.rnscreens.gamma.tabs.container.CustomBottomNavigationView.setSelectedItemIdWithActionOrigin$react_native_screens_release (CustomBottomNavigationView.kt:22)
at com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.performSelectedTabUpdate (TabsContainer.kt:498)
at com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.performSelectedTabUpdateIfNeeded (TabsContainer.kt:460)
at com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.performContainerUpdate (TabsContainer.kt:438)
at com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.flushPendingUpdates (TabsContainer.kt:194)
at com.swmansion.rnscreens.gamma.tabs.host.TabsHost.didMountItems (TabsHost.kt:197)
at com.facebook.react.fabric.FabricUIManager$MountItemDispatchListener.didMountItems (FabricUIManager.java:1381)
at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems (MountItemDispatcher.kt:273)
at com.facebook.react.fabric.mounting.MountItemDispatcher.tryDispatchMountItems (MountItemDispatcher.kt:93)
at com.facebook.react.fabric.FabricUIManager$2.runGuarded (FabricUIManager.java:930)
at com.facebook.react.bridge.GuardedRunnable.run (GuardedRunnable.kt:19)
at com.facebook.react.fabric.FabricUIManager.scheduleMountItem (FabricUIManager.java:933)
at com.swmansion.reanimated.NativeProxy.performOperations (NativeProxy.kt)
at com.swmansion.reanimated.NodesManager.performOperations (NodesManager.kt:121)
at com.swmansion.reanimated.NodesManager.performOperationsRespectingDrawPass$react_native_reanimated_release (NodesManager.kt:137)
at com.swmansion.reanimated.NodesManager.onEventDispatch (NodesManager.kt:212)
at com.facebook.react.uimanager.events.FabricEventDispatcher.dispatchEvent (FabricEventDispatcher.kt:46)
at com.swmansion.rnscreens.gamma.tabs.screen.TabsScreenEventEmitter.emitOnDidAppear (TabsScreenEventEmitter.kt:23)
at com.swmansion.rnscreens.gamma.tabs.screen.TabsScreenFragment.onResume (TabsScreenFragment.kt:28)
at androidx.fragment.app.Fragment.performResume (Fragment.java:3210)
at androidx.fragment.app.FragmentStateManager.resume (FragmentStateManager.java:666)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:310)
at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:114)
at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1685)
at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3319)
at androidx.fragment.app.FragmentManager.dispatchResume (FragmentManager.java:3251)
at androidx.fragment.app.Fragment.performResume (Fragment.java:3219)
at androidx.fragment.app.FragmentStateManager.resume (FragmentStateManager.java:666)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:310)
at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:114)
at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1685)
at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3319)
at androidx.fragment.app.FragmentManager.dispatchResume (FragmentManager.java:3251)
at androidx.fragment.app.FragmentController.dispatchResume (FragmentController.java:285)
at androidx.fragment.app.FragmentActivity.onResumeFragments (FragmentActivity.java:333)
at androidx.fragment.app.FragmentActivity.onPostResume (FragmentActivity.java:322)
at androidx.appcompat.app.AppCompatActivity.onPostResume (AppCompatActivity.java:232)
at android.app.Activity.performResume (Activity.java:9718)
at android.app.ActivityThread.performResumeActivity (ActivityThread.java:6239)
Reanimated configuration
This crash involves Reanimated synchronously flushing mount items from within the onDidAppear event dispatch. The relevant versions are react-native-reanimated 4.4.1 and react-native-worklets 0.9.2, with the following static feature flags in our package.json:
"reanimated": {
"staticFeatureFlags": {
"ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS": true,
"IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS": true
}
}
The ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS: true flag is likely significant here: it causes NodesManager.onEventDispatch to synchronously performOperations → FabricUIManager.scheduleMountItem, which is what drives the re-entrant mount flush (TabsHost.didMountItems → TabsContainer.updateSelectedFragment → commitNowAllowingStateLoss) while the FragmentManager is still executing the resume transaction. It's possible the crash only reproduces with this flag enabled.
Related issues
The exception class matches #2318 / #1506, but those occur on the legacy ScreenContainer.onUpdate path — this report is on the new Gamma Tabs API (gamma.tabs.container.TabsContainer.updateSelectedFragment) and is triggered re-entrantly from Fragment.onResume. PR #3996 touches the same setSelectedItemIdWithActionOrigin code path.
Steps to reproduce
This was captured in the wild (Google Play Console) on a production release build, so we do not have deterministic local repro steps yet. Based on the stack trace it occurs during activity resume while a Gamma Tabs container has pending updates — likely returning to the app/foregrounding the activity while a tab selection update is in flight. The relevant sequence is Activity.onResume → TabsScreenFragment.onResume → emitOnDidAppear → Reanimated synchronous mount flush → TabsContainer.updateSelectedFragment → commitNowAllowingStateLoss.
- Use the Gamma Tabs API (bottom tab navigation) with Reanimated.
- Trigger a tab selection update around the time the activity is resuming/returning to foreground.
- Crash on resume.
Snack or a link to a repository
No reproduction available — captured via Google Play Console crash reporting on a production release build. (I can provide additional Play Console details if helpful.)
Screens version
4.25.2
React Native version
0.85.3
Platforms
Android
JavaScript runtime
Hermes
Workflow
Expo managed workflow
Architecture
Fabric (New Architecture)
Build type
Release mode
Device
Real device
Device model
Samsung Galaxy S23 (Android 16, SDK 36)
Acknowledgements
Yes
Description
Our app received a crash report from Google Play Console on a release build using the new Gamma Tabs API (
com.swmansion.rnscreens.gamma.tabs).The crash is a
java.lang.IllegalStateException: FragmentManager is already executing transactions, thrown fromTabsContainer.updateSelectedFragment(TabsContainer.kt:563) when it callscommitNowAllowingStateLoss.The root cause appears to be re-entrancy of the
FragmentManager. The chain is:FragmentManageris in the middle ofdispatchResume/moveToState(an active fragment transaction is executing).TabsScreenFragment.onResume(TabsScreenFragment.kt:28) firesemitOnDidAppear.onDidAppearevent is dispatched synchronously to Reanimated (NodesManager.onEventDispatch→performOperationsRespectingDrawPass→FabricUIManager.scheduleMountItem), which synchronously flushes mount items while still inside the resume pass.TabsHost.didMountItems→TabsContainer.flushPendingUpdates→performContainerUpdate→performSelectedTabUpdate→updateSelectedFragment, which callscommitNowAllowingStateLoss.ensureExecReadythrowsFragmentManager is already executing transactions.So a synchronous tab/mount update is being committed re-entrantly from within
Fragment.onResume, which theFragmentManagerdoes not allow.Expected: Pending tab updates committed during/triggered by a fragment lifecycle callback should be deferred (e.g. posted) rather than committed synchronously, so they never re-enter a
FragmentManagerthat is already executing transactions.Full stack trace
Reanimated configuration
This crash involves Reanimated synchronously flushing mount items from within the
onDidAppearevent dispatch. The relevant versions arereact-native-reanimated4.4.1 andreact-native-worklets0.9.2, with the following static feature flags in ourpackage.json:The
ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS: trueflag is likely significant here: it causesNodesManager.onEventDispatchto synchronouslyperformOperations→FabricUIManager.scheduleMountItem, which is what drives the re-entrant mount flush (TabsHost.didMountItems→TabsContainer.updateSelectedFragment→commitNowAllowingStateLoss) while theFragmentManageris still executing the resume transaction. It's possible the crash only reproduces with this flag enabled.Related issues
The exception class matches #2318 / #1506, but those occur on the legacy
ScreenContainer.onUpdatepath — this report is on the new Gamma Tabs API (gamma.tabs.container.TabsContainer.updateSelectedFragment) and is triggered re-entrantly fromFragment.onResume. PR #3996 touches the samesetSelectedItemIdWithActionOrigincode path.Steps to reproduce
This was captured in the wild (Google Play Console) on a production release build, so we do not have deterministic local repro steps yet. Based on the stack trace it occurs during activity resume while a Gamma Tabs container has pending updates — likely returning to the app/foregrounding the activity while a tab selection update is in flight. The relevant sequence is
Activity.onResume→TabsScreenFragment.onResume→emitOnDidAppear→ Reanimated synchronous mount flush →TabsContainer.updateSelectedFragment→commitNowAllowingStateLoss.Snack or a link to a repository
No reproduction available — captured via Google Play Console crash reporting on a production release build. (I can provide additional Play Console details if helpful.)
Screens version
4.25.2
React Native version
0.85.3
Platforms
Android
JavaScript runtime
Hermes
Workflow
Expo managed workflow
Architecture
Fabric (New Architecture)
Build type
Release mode
Device
Real device
Device model
Samsung Galaxy S23 (Android 16, SDK 36)
Acknowledgements
Yes