software-mansion / software-mansion/react-native-screens
[Gamma Tabs] Crash: "FragmentManager is already executing transactions" in TabsContainer.updateSelectedFragment during Fragment.onResume
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.7k
- Forks
- 713
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 71
Description
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:
FragmentManageris in the middle ofdispatchResume/moveToState(an active fragment transaction is executing).TabsScreenFragment.onResume(TabsScreenFragment.kt:28) firesemitOnDidAppear.- That
onDidAppearevent 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 callscommitNowAllowingStateLoss. - Because a fragment transaction is already executing (step 1),
ensureExecReadythrowsFragmentManager 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
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt, especially flushPendingUpdates, performContainerUpdate, and updateSelectedFragment, then inspect TabsScreenFragment.kt:28 and TabsHost.kt:197. Trace how the onDidAppear event reaches the pending-update flush during resume. Done means tab updates triggered during fragment lifecycle callbacks no longer re-enter FragmentManager, with the crash path covered by an appropriate validation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin, react-native
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100