Skip to content

Drive shared animation backend from Fabric frame callback (#57400)#57400

Open
bartlomiejbloniarz wants to merge 1 commit into
mainfrom
export-D110321362
Open

Drive shared animation backend from Fabric frame callback (#57400)#57400
bartlomiejbloniarz wants to merge 1 commit into
mainfrom
export-D110321362

Conversation

@bartlomiejbloniarz

@bartlomiejbloniarz bartlomiejbloniarz commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary:

In this diff we drop the custom choreographer for the backend on Android and instead plug into the one in FabricUIManager. This reduces the area for possible mistakes, makes it clearer how Fabric interacts with animation on a per-frame basis, and simplifies the flow around invalidation and cleanup of the React instance.

The crash this is meant to avoid comes from having two separate frame callback lifecycles. The old AnimationBackendChoreographer owned a self-reposting callback that could keep driving FabricUIManagerBinding.driveAnimationBackend independently from Fabric's own lifecycle. During React instance teardown, FabricUIManager.invalidate() pauses Fabric's frame callback and then unregisters the native binding. If a separate backend callback survives that sequence, it can invoke the binding after the native side has been uninstalled.

The shared animation backend is now driven from Fabric's existing DISPATCH_UI frame callback after mount items are dispatched. The Android AnimationChoreographer implementation only owns backend pause/resume state and conditionally forwards active frames to the shared backend.

Threading-wise:

  • If invalidation happens before a frame starts, mDestroyed makes the frame no-op.
  • If invalidation races with an already-running frame, ReactChoreographer.removeFrameCallback is serialized with callback execution via the callbackQueues monitor, so onHostPause() waits for the current DISPATCH_UI callback to finish before unregister() tears down the native binding.
  • If the frame reposts itself in schedule(), the blocked removal observes and removes that callback before teardown continues.

Changelog:
[Android][Fixed] - Drive the shared animation backend from Fabric's frame callback during React instance teardown

Reviewed By: javache, zeyap

Differential Revision: D110321362

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@meta-codesync

meta-codesync Bot commented Jul 1, 2026

Copy link
Copy Markdown

@bartlomiejbloniarz has exported this pull request. If you are a Meta employee, you can view the originating Diff in D110321362.

@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner p: Facebook Partner: Facebook labels Jul 1, 2026
@meta-codesync meta-codesync Bot force-pushed the export-D110321362 branch from 7fef845 to 14a4667 Compare July 1, 2026 13:58
@meta-codesync meta-codesync Bot changed the title Drive shared animation backend from Fabric frame callback Jul 1, 2026
meta-codesync Bot pushed a commit that referenced this pull request Jul 1, 2026
Summary:

In this diff we drop the custom choreographer for the backend on Android and instead plug into the one in `FabricUIManager`. This reduces the area for possible mistakes, makes it clearer how Fabric interacts with animation on a per-frame basis, and simplifies the flow around invalidation and cleanup of the React instance.

The crash this is meant to avoid comes from having two separate frame callback lifecycles. The old `AnimationBackendChoreographer` owned a self-reposting callback that could keep driving `FabricUIManagerBinding.driveAnimationBackend` independently from Fabric's own lifecycle. During React instance teardown, `FabricUIManager.invalidate()` pauses Fabric's frame callback and then unregisters the native binding. If a separate backend callback survives that sequence, it can invoke the binding after the native side has been uninstalled.

The shared animation backend is now driven from Fabric's existing `DISPATCH_UI` frame callback after mount items are dispatched. The Android `AnimationChoreographer` implementation only owns backend pause/resume state and conditionally forwards active frames to the shared backend.

Threading-wise:
- If invalidation happens before a frame starts, `mDestroyed` makes the frame no-op.
- If invalidation races with an already-running frame, `ReactChoreographer.removeFrameCallback` is serialized with callback execution via the `callbackQueues` monitor, so `onHostPause()` waits for the current `DISPATCH_UI` callback to finish before `unregister()` tears down the native binding.
- If the frame reposts itself in `schedule()`, the blocked removal observes and removes that callback before teardown continues.

Changelog:
[Android][Fixed] - Drive the shared animation backend from Fabric's frame callback during React instance teardown

Differential Revision: D110321362
@meta-codesync meta-codesync Bot force-pushed the export-D110321362 branch from 14a4667 to 2fb8422 Compare July 1, 2026 14:30
meta-codesync Bot pushed a commit that referenced this pull request Jul 1, 2026
Summary:

In this diff we drop the custom choreographer for the backend on Android and instead plug into the one in `FabricUIManager`. This reduces the area for possible mistakes, makes it clearer how Fabric interacts with animation on a per-frame basis, and simplifies the flow around invalidation and cleanup of the React instance.

The crash this is meant to avoid comes from having two separate frame callback lifecycles. The old `AnimationBackendChoreographer` owned a self-reposting callback that could keep driving `FabricUIManagerBinding.driveAnimationBackend` independently from Fabric's own lifecycle. During React instance teardown, `FabricUIManager.invalidate()` pauses Fabric's frame callback and then unregisters the native binding. If a separate backend callback survives that sequence, it can invoke the binding after the native side has been uninstalled.

The shared animation backend is now driven from Fabric's existing `DISPATCH_UI` frame callback after mount items are dispatched. The Android `AnimationChoreographer` implementation only owns backend pause/resume state and conditionally forwards active frames to the shared backend.

Threading-wise:
- If invalidation happens before a frame starts, `mDestroyed` makes the frame no-op.
- If invalidation races with an already-running frame, `ReactChoreographer.removeFrameCallback` is serialized with callback execution via the `callbackQueues` monitor, so `onHostPause()` waits for the current `DISPATCH_UI` callback to finish before `unregister()` tears down the native binding.
- If the frame reposts itself in `schedule()`, the blocked removal observes and removes that callback before teardown continues.

Changelog:
[Android][Fixed] - Drive the shared animation backend from Fabric's frame callback during React instance teardown

Reviewed By: zeyap

Differential Revision: D110321362
@meta-codesync meta-codesync Bot force-pushed the export-D110321362 branch from 2fb8422 to 37faa1b Compare July 1, 2026 16:21
Summary:

In this diff we drop the custom choreographer for the backend on Android and instead plug into the one in `FabricUIManager`. This reduces the area for possible mistakes, makes it clearer how Fabric interacts with animation on a per-frame basis, and simplifies the flow around invalidation and cleanup of the React instance.

The crash this is meant to avoid comes from having two separate frame callback lifecycles. The old `AnimationBackendChoreographer` owned a self-reposting callback that could keep driving `FabricUIManagerBinding.driveAnimationBackend` independently from Fabric's own lifecycle. During React instance teardown, `FabricUIManager.invalidate()` pauses Fabric's frame callback and then unregisters the native binding. If a separate backend callback survives that sequence, it can invoke the binding after the native side has been uninstalled.

The shared animation backend is now driven from Fabric's existing `DISPATCH_UI` frame callback after mount items are dispatched. The Android `AnimationChoreographer` implementation only owns backend pause/resume state and conditionally forwards active frames to the shared backend.

Threading-wise:
- If invalidation happens before a frame starts, `mDestroyed` makes the frame no-op.
- If invalidation races with an already-running frame, `ReactChoreographer.removeFrameCallback` is serialized with callback execution via the `callbackQueues` monitor, so `onHostPause()` waits for the current `DISPATCH_UI` callback to finish before `unregister()` tears down the native binding.
- If the frame reposts itself in `schedule()`, the blocked removal observes and removes that callback before teardown continues.

Changelog:
[Android][Fixed] - Drive the shared animation backend from Fabric's frame callback during React instance teardown

Reviewed By: javache, zeyap

Differential Revision: D110321362
meta-codesync Bot pushed a commit that referenced this pull request Jul 1, 2026
Summary:

In this diff we drop the custom choreographer for the backend on Android and instead plug into the one in `FabricUIManager`. This reduces the area for possible mistakes, makes it clearer how Fabric interacts with animation on a per-frame basis, and simplifies the flow around invalidation and cleanup of the React instance.

The crash this is meant to avoid comes from having two separate frame callback lifecycles. The old `AnimationBackendChoreographer` owned a self-reposting callback that could keep driving `FabricUIManagerBinding.driveAnimationBackend` independently from Fabric's own lifecycle. During React instance teardown, `FabricUIManager.invalidate()` pauses Fabric's frame callback and then unregisters the native binding. If a separate backend callback survives that sequence, it can invoke the binding after the native side has been uninstalled.

The shared animation backend is now driven from Fabric's existing `DISPATCH_UI` frame callback after mount items are dispatched. The Android `AnimationChoreographer` implementation only owns backend pause/resume state and conditionally forwards active frames to the shared backend.

Threading-wise:
- If invalidation happens before a frame starts, `mDestroyed` makes the frame no-op.
- If invalidation races with an already-running frame, `ReactChoreographer.removeFrameCallback` is serialized with callback execution via the `callbackQueues` monitor, so `onHostPause()` waits for the current `DISPATCH_UI` callback to finish before `unregister()` tears down the native binding.
- If the frame reposts itself in `schedule()`, the blocked removal observes and removes that callback before teardown continues.

Changelog:
[Android][Fixed] - Drive the shared animation backend from Fabric's frame callback during React instance teardown

Reviewed By: javache, zeyap

Differential Revision: D110321362
@meta-codesync meta-codesync Bot force-pushed the export-D110321362 branch 2 times, most recently from 503a790 to 6450a73 Compare July 1, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook p: Software Mansion Partner: Software Mansion Partner

1 participant