feat: add onStop callback for animation lifecycle (iOS + Android)#353
feat: add onStop callback for animation lifecycle (iOS + Android)#353hlus wants to merge 2 commits into
Conversation
|
I reviewed the native signal each backend wires up, and verified the behavior with a
Since the legacy backend only exists for internal testing at this point, I'd drop the two legacy wirings rather than fix them. The
|
thx for review, if I need to fix this let me know I will do my best 🫡 |
…entation Removed unnecessary onStop wiring in the legacy runtime as it lacks a reliable signal for playback completion. Updated comments to clarify the behavior of onStop in both legacy and new runtimes, ensuring accurate documentation for users.
|
@mfazekas Android new runtime (frame storm): the render loop now stops calling iOS new runtime (double-fire at mount): settle events are now coalesced through a 150ms debounce before invoking iOS legacy: dropped the Android legacy: same call — removed the For both legacy backends Also added your harness test as a permanent regression test ( Verified: ran the full harness suite on iOS (new runtime) locally — 28/28 suites, 208/208 tests passing, including the new |
|
That'd be great, thanks! Here's what we'd like this PR to converge on:
|
|
Re-verified your update on device with the harness test: both platforms now report A few asks before merge:
Optional follow-up (not blocking): while settled, the Android loop stops advancing but still draws identical frames every vsync — an early-out like the paused path would complete the CPU win. |

feat: onStop callback for RiveView animation lifecycle
RiveViewhas no way to tell JS when playback stops — a common gap for splash-screen-style usage, where a one-shot animation plays once and the app needs to navigate away exactly when it finishes. This adds anonStopprop, called when the animation/state machine stops playing:onStopis not called bypause()— only when playback naturally comes to rest.RiveViewMethodsdoesn't expose astop()today (onlyplay/pause/reset), so in practice this only fires when a non-looping animation reaches its end or a state machine settles into a state with no further transitions — exactly the splash-screen case.Each backend surfaces this differently, so the implementation is four separate wirings:
RiveReactNativeViewto Rive'sRivePlayerDelegateand setsriveView.playerDelegate = self(alongside the existingstateMachineDelegate), forwardingplayer(stoppedWithModel:). Verified against the actualRiveRuntime.xcframeworkswiftinterface —RivePlayerDelegaterequires all five methods (play/pause/loop/stop/advance), so the other four are no-ops.StateMachine.settledStream() -> AsyncStream<Void>, consumed via a cancellableTaskperRiveinstance and torn down alongside the existing config/cleanup paths.RiveFileController.Listeneron theRiveAnimationView(mirroring how event listeners are already wired), forwardingnotifyStop.CommandQueue.settledFlow(aSharedFlow<StateMachineHandle>, since the queue is shared across views) filtered to this view's ownStateMachineHandle.Verified the API signatures for both experimental-runtime paths (
settledStream/settledFlow) and the legacyRivePlayerDelegate/RiveFileController.Listenerdirectly against the vendoredRiveRuntime.xcframeworkswiftinterface and the decompiledrive-androidAAR, rather than from docs alone.Docs: added an "Animation Lifecycle" section to the README and a row in the runtime feature comparison table.
Testing:
yarn typecheck,yarn lint, andyarn testall pass. Built the example app for real on all four backend combinations (USE_RIVE_LEGACYon/off × iOS/Android) viaxcodebuild/gradlew, not just the JS layer — this caught one real compile error (a Kotlin smart-cast issue onstateMachineHandlein the Android new-runtimeobserveSettledcall site), now fixed.