software-mansion / software-mansion/react-native-screens
[New Arch] react-native-screens Fragment accumulation bloats onSaveInstanceState → TransactionTooLargeException on activityStopped
@t0maboro is already working on this.
Since Aug 12, 2026.
- Dominant language
- TypeScript
- Stars
- 3.7k
- Forks
- 713
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 71
Description
Description
Under the New Architecture (Fabric), react-native-screens mounts one native Fragment per pushed screen (ScreenStackFragment, each with a nested childFragmentManager/ScreenFragment). On a deep push-navigation stack these accumulate in the host Activity's saved state. When the OS stops the Activity (app backgrounded), onSaveInstanceState serialises all of them, and in a real app the parcel exceeds the Binder transaction budget → fatal, unhandled android.os.TransactionTooLargeException on activityStopped.
This is a save-time crash, distinct from the well-known restore-time crashes (#17, #3317) that AutoRemovingFragment / RNScreensFragmentFactory / super.onCreate(null) address. Those do not help here: the oversized parcel is rejected by the Binder at save time, before any restore.
Production crash evidence. Real TooLargeTool-style breakdown from a shipping app on this stack (the parcel that crashed at activityStopped, ~639 KB):
androidx.lifecycle.BundlableSavedStateRegistry.key [637 KB]
android:support:activity-result [208 KB]
android:support:fragments [428 KB] <- ~150 ScreenStackFragment/ScreenFragment entries
fragment_<uuid> [2868]
childFragmentManager [2176]
fragment_<uuid> [1752]
childFragmentManager [1068]
... (x150)
- Device: Samsung Galaxy A57 (SM-A576B), Android 16, 7.8 GB RAM — not low-memory.
- Each real fragment contributes ~2.8 KB (FragmentManager metadata + the nested
childFragmentManager); at 150+ retained fragments this is 428 KB.
About the reproduction (please read). The linked repo demonstrates the accumulation mechanism — it auto-pushes a native-stack screen and logs, on background, the saved-state parcel size and that android:support:fragments is present (regHasFragments=true). It does not, by itself, cross the ~1 MB limit and crash, and that is worth stating plainly: with trivial screens the saved state is only ~75 bytes/fragment (measured on API 34), so a toy stack stays far under the limit. The production crash is emergent from real screens × deep navigation (real screens save ~2.8 KB/fragment; long sessions reach 150+ retained fragments). The repo shows the code path and lets you watch the parcel grow with depth; the crash-scale evidence is the production bundle above.
Why this seems in scope for react-native-screens. RNS already declares its fragments non-restorable — it discards restored fragment state and rebuilds from JS (AutoRemovingFragment, and the discussion in #96). But it still lets that state be saved into the Activity parcel, where on deep stacks it triggers TransactionTooLargeException. Since RNS never consumes the saved fragment state, arguably it should not let it balloon the save parcel — e.g. opt its ScreenStackFragments out of instance-state saving, or expose a supported way to bound it.
App-side workaround (and a gotcha). Stripping the fragment state in MainActivity.onSaveInstanceState stops the crash — RNS rebuilds fine from JS. Note: on androidx.fragment 1.8.x the FragmentManager state is saved via SavedStateRegistry, so it is nested — the classic top-level outState.remove("android:support:fragments") is a no-op (measured: 0 bytes removed). It must be reached inside the registry bundle:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.getBundle("androidx.lifecycle.BundlableSavedStateRegistry.key")
?.remove("android:support:fragments")
}
This depends on internal androidx key strings, which is why a first-party RNS option would be preferable.
Steps to reproduce
Reproduction repo: https://github.com/MisterMunchkin/rns-4487-repro
git clone https://github.com/MisterMunchkin/rns-4487-repro && cd rns-4487-repro && npm installnpx expo prebuild --platform android --cleanthennpx expo run:androidon an Android 13+ emulator/device. New Architecture is enabled viaapp.json; a parcel-size logger is injected intoMainActivitybyplugins/with-parcel-logger.js.- In a second terminal:
adb logcat -s RNSRepro. - The app auto-pushes a self-referential native-stack screen to
TARGET_DEPTH. When it settles, enable Developer Options → "Don't keep activities" (forces the save+stop deterministically on an emulator; production hits it via natural memory pressure), then press Home. RNSReprologsonSaveInstanceState parcel=<N> bytes | regHasFragments=true— confirming the react-native-screens fragments accumulate in the Activity's saved state (nested underandroidx.lifecycle.BundlableSavedStateRegistry.key). RaiseTARGET_DEPTH(or run against content-heavy screens) to watch the parcel climb toward the Binder limit; at production scale (real screens, 150+ fragments ≈ 428 KB) it crosses ~639 KB and throwsTransactionTooLargeExceptionatactivityStopped.
Snack or a link to a repository
https://github.com/MisterMunchkin/rns-4487-repro
Screens version
4.18.0
React Native version
0.81.5
Platforms
Android
JavaScript runtime
Hermes
Workflow
Expo bare workflow (expo prebuild / CNG; androidx.fragment 1.8.9)
Device
Real device (production crash) + Android emulator (mechanism)
Device model
Samsung Galaxy A57 (SM-A576B), Android 16
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.
Assessment
This issue has not been assessed yet.