Repository navigation
fix(android): fix FlatList crash when view loses and regains native view in merged Fabric transaction - #59761
Open
parthrana-27 wants to merge 1 commit into
Conversation
parthrana-27
force-pushed
the
fix/59724-flatlist-crash-delete-order-android
branch
from
November 2, 2026 15:15
bf302d0 to
60ada85
Compare
When multiple Fabric transactions are merged, a tag can appear in both a DELETE mutation (from an earlier transaction) and a CREATE mutation (from a later transaction) within the same merged batch. The existing optimisation writes all Delete mount items last (after Updates and Creates) for space efficiency. This is safe when a single transaction is processed, because the differ guarantees DELETE always precedes CREATE for the same tag within one transaction. However, when transactions are merged the guarantee no longer holds: DELETEs from the earlier transaction land after CREATEs from the later one, so the Java side receives layout UPDATE instructions for a tag whose native view has already been re-created and re-parented. That triggers an IllegalStateException in ReactViewGroup.isViewClipped (check failed: parent !== this). Fix (two-part): 1. FabricMountingManager.cpp (root cause) In the Delete mutation branch of executeMount, detect whether a CREATE for the same tag exists later in the same batch. If so, promote the DELETE into cppCommonMountItems (which preserves mutation order) instead of deferring it to cppDeleteMountItems. For the common case (no recycled tag) the delete-last optimisation is preserved unchanged. 2. ReactViewGroup.kt (defensive guard) Replace the hard crash check(parent === this) in isViewClipped with a soft-exception path that logs the anomaly and returns true (treating the view as clipped) when parent !== this. This ensures any similar ordering edge-cases in the future degrade gracefully rather than producing a fatal crash. Fixes react#59724
parthrana-27
force-pushed
the
fix/59724-flatlist-crash-delete-order-android
branch
from
November 3, 2026 06:08
60ada85 to
0d88937
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Fixes #59724
On Android, a
FlatList(or any component using Fabric) can crash with:when a View loses its native view and gets it back within the same JavaScript task — for example when a list item is unmounted and immediately remounted due to rapid state changes on a low-end Android device.
Root cause
FabricMountingManager::executeMountseparatesDeletemount items from the rest and writes them last as a space-efficiency optimisation. A comment in the original code acknowledged the problem:When two transactions are merged, the same tag can appear in a
Deletemutation (from the earlier transaction) and aCreatemutation (from the later transaction) inside the same merged batch. Because deletes are written last, the Java side receives a layoutUpdatefor the tag while the old native view is still attached to a differentReactViewGroup. When that view's parent is later checked inisViewClipped,check(parent === this)fails withIllegalStateException.Fix (two parts)
1.
FabricMountingManager.cpp— root causeIn the
Deletemutation branch ofexecuteMount, scan the current batch for a subsequentCreatewith the same tag. If one exists, promote theDeleteintocppCommonMountItemsso it is emitted in mutation order (before theCreate), rather than being deferred to the end. For the common case where no tag is recycled in the same batch, the delete-last optimisation is fully preserved.2.
ReactViewGroup.kt— defensive guardReplace the hard
check(parent === this)inisViewClippedwith a soft-exception path: whenparent !== this, log aReactNoCrashSoftException(surfaced in crash reporting) and returntrue(treat the view as clipped/removed). This prevents a fatal crash for any ordering edge-cases not yet covered by part 1, and is consistent with thelogSoftExceptionalready present a few lines above in the same function.Changelog:
[ANDROID] [FIXED] - Fix
IllegalStateExceptioncrash inReactViewGroup.isViewClippedwhen a FlatList item loses and regains its native view within a merged Fabric transaction batchTest Plan:
Reproducer: https://github.com/Nezz/expo-repro/tree/repro/MergedTransactionDeleteOrder
npm install.cd android && ./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8aadb install -r app/build/outputs/apk/release/app-release.apkWithout this fix: the app crashes immediately with
IllegalStateException: Check failedatReactViewGroup.isViewClipped.With this fix: the app no longer crashes.
Unit tests: The fix is in native C++ / Kotlin and does not change any JS or TypeScript surface. The existing
ReactViewGroupunit tests pass unchanged. No new test infrastructure is required — the reproducer above is the regression test.Static analysis:
ruff/clang-formatnot applicable; the C++ change follows the surrounding code style and the Kotlin change matches the existinglogSoftExceptionpattern already present inisViewClipped.