Skip to content

[Gamma Tabs] Crash: "FragmentManager is already executing transactions" in TabsContainer.updateSelectedFragment during Fragment.onResume #4199

Description

@tomekzaw

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:

  1. FragmentManager is in the middle of dispatchResume / moveToState (an active fragment transaction is executing).
  2. TabsScreenFragment.onResume (TabsScreenFragment.kt:28) fires emitOnDidAppear.
  3. That onDidAppear event is dispatched synchronously to Reanimated (NodesManager.onEventDispatchperformOperationsRespectingDrawPassFabricUIManager.scheduleMountItem), which synchronously flushes mount items while still inside the resume pass.
  4. The flushed mount items reach TabsHost.didMountItemsTabsContainer.flushPendingUpdatesperformContainerUpdateperformSelectedTabUpdateupdateSelectedFragment, which calls commitNowAllowingStateLoss.
  5. 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 performOperationsFabricUIManager.scheduleMountItem, which is what drives the re-entrant mount flush (TabsHost.didMountItemsTabsContainer.updateSelectedFragmentcommitNowAllowingStateLoss) 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.onResumeTabsScreenFragment.onResumeemitOnDidAppear → Reanimated synchronous mount flush → TabsContainer.updateSelectedFragmentcommitNowAllowingStateLoss.

  1. Use the Gamma Tabs API (bottom tab navigation) with Reanimated.
  2. Trigger a tab selection update around the time the activity is resuming/returning to foreground.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    missing-reproThis issue need minimum repro scenarioplatform:androidIssue related to Android part of the library

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions