Skip to content

Commit 53369ed

Browse files
Skip dropped mapped node in PropsAnimatedNode.updateView instead of crashing (#57298)
Summary: `PropsAnimatedNode.updateView()` iterates its mapped property nodes every frame and calls `requireNotNull(node) { "Mapped property node does not exist" }` for each one. When a connected component is unmounted (for example during a navigation transition), its child animated nodes can be dropped from `NativeAnimatedNodesManager` while a frame callback for the prop node is still in flight. On the next `updateView` the mapped node is gone, `requireNotNull` throws, and the app crashes: ``` java.lang.IllegalArgumentException: Mapped property node does not exist at com.facebook.react.animated.PropsAnimatedNode.updateView(PropsAnimatedNode.kt) at com.facebook.react.animated.NativeAnimatedNodesManager.updateNodes(NativeAnimatedNodesManager.java) at com.facebook.react.animated.NativeAnimatedNodesManager.runUpdates(NativeAnimatedNodesManager.java) ``` This is a teardown race: by the time the mapped node has been removed, the connected view is being torn down, so there is no meaningful value to write for that prop on this frame. This change skips a missing mapped node instead of throwing. It mirrors the guard already at the top of the same method, which returns early when the view itself is gone (`connectedViewTag == -1`), and matches the lenient behavior on iOS, where `RCTPropsAnimatedNode` iterates its parent nodes with `isKindOfClass:` checks and a missing (`nil`) node is simply skipped. Fixes #37267. ## Changelog: [ANDROID] [FIXED] - Prevent "Mapped property node does not exist" crash in `PropsAnimatedNode.updateView` when a mapped node is removed during an in-flight native animation Pull Request resolved: #57298 Test Plan: The crash is a timing-dependent race, so it reproduces probabilistically rather than deterministically. Using the repro from #37267 (a screen with many simultaneous native-driver animations, navigating in and out repeatedly) and increasing **Animator duration scale** in Developer Options widens the window and makes it reproduce reliably on lower-end devices. - **Before:** rapidly navigating away from a screen with running native-driver animations crashes with `IllegalArgumentException: Mapped property node does not exist`. - **After:** the stale node is skipped and no crash occurs. - **Happy path unaffected:** when all mapped nodes are present, behavior is identical. The change only adds an early `continue` for the already-removed-node case, so no prop update is lost for live nodes. Reviewed By: zeyap, christophpurrer Differential Revision: D109253948 Pulled By: fabriziocucci fbshipit-source-id: 76c1a4c89e7cdd4476a31866d4871e90ddb98324
1 parent dc4d5e8 commit 53369ed

2 files changed

Lines changed: 25 additions & 1 deletion

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,12 @@ internal class PropsAnimatedNode(
7575
}
7676
for ((key, value) in propNodeMapping) {
7777
val node = nativeAnimatedNodesManager.getNodeById(value)
78-
requireNotNull(node) { "Mapped property node does not exist" }
78+
// The mapped node can be dropped mid-teardown (e.g. component unmounts during
79+
// navigation) while this prop update is still in flight. Skip it instead of
80+
// throwing, mirroring the connectedViewTag == -1 guard.
81+
if (node == null) {
82+
continue
83+
}
7984
if (node is StyleAnimatedNode) {
8085
node.collectViewUpdates(propMap)
8186
} else if (node is ValueAnimatedNode) {

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,25 @@ class NativeAnimatedNodeTraversalTest {
164164
verifyNoMoreInteractions(uiManagerMock)
165165
}
166166

167+
@Test
168+
fun testUpdateViewSkipsDroppedMappedNode() {
169+
// Regression test for #37267: a mapped child node can be dropped (e.g. its
170+
// component unmounts during navigation) while the props node still has an
171+
// in-flight update. updateView() must skip the missing node instead of
172+
// throwing "Mapped property node does not exist".
173+
createSimpleAnimatedViewWithOpacity()
174+
175+
// Drop the mapped style node (id 2) that the props node (id 3) references,
176+
// reproducing the teardown race.
177+
nativeAnimatedNodesManager.dropAnimatedNode(2)
178+
assertThat(nativeAnimatedNodesManager.getNodeById(2)).isNull()
179+
180+
val propsNode = nativeAnimatedNodesManager.getNodeById(3) as PropsAnimatedNode
181+
182+
// The missing mapped node is skipped: updateView completes without throwing.
183+
propsNode.updateView()
184+
}
185+
167186
@Test
168187
fun testFramesAnimationWithFinalFrameBeingDifferentFromToValue() {
169188
createSimpleAnimatedViewWithOpacity()

0 commit comments

Comments
 (0)