From aa581db11596eeb5ce286fc9ad2e44fa6108bf52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Mon, 13 Jul 2026 08:27:03 +0200 Subject: [PATCH 1/2] fix(android): don't serve or re-cache the VMI snapshot after dispose (legacy) Port of 6e55131 from android/src/main: lastKnownViewModelInstance survived dispose, so a JS caller still holding the view ref got the retained, already-released instance back (first property access throws "Cannot acquire a disposed object") instead of null. Clear it on the disposing detach, and resolve null from the main-thread read once willDispose is set so a refresh posted before dispose can't re-cache a released instance afterwards. --- android/src/legacy/java/com/rive/RiveReactNativeView.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/android/src/legacy/java/com/rive/RiveReactNativeView.kt b/android/src/legacy/java/com/rive/RiveReactNativeView.kt index 5b9244f2..3c27882a 100644 --- a/android/src/legacy/java/com/rive/RiveReactNativeView.kt +++ b/android/src/legacy/java/com/rive/RiveReactNativeView.kt @@ -110,6 +110,9 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) { if (willDispose) { riveAnimationView?.dispose() removeEventListeners() + // Post-dispose reads must resolve null, not the retained (released) + // instance; also stops pinning it for the view's remaining lifetime. + lastKnownViewModelInstance = null } super.onDetachedFromWindow() } @@ -182,6 +185,10 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) { private var lastKnownViewModelInstance: ViewModelInstance? = null private fun readViewModelInstanceOnMain(): ViewModelInstance? { + // A refresh posted before dispose() can run after it; reading the + // torn-down controller's stale list here would re-cache a released + // instance right after onDetachedFromWindow cleared it. + if (willDispose) return null val stateMachines = riveAnimationView?.controller?.stateMachines return if (!stateMachines.isNullOrEmpty()) { stateMachines.first().viewModelInstance From 56e0c8ae8827909b2c3ecb849f5da47c0ef6c609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Mon, 13 Jul 2026 08:27:03 +0200 Subject: [PATCH 2/2] perf(android): reuse a cached main-thread Handler for snapshot refreshes (legacy) Port of 84e6d26 from android/src/main: the async hook's ref path polls getViewModelInstance() every 50ms; allocating a Handler per off-main call was avoidable churn. --- android/src/legacy/java/com/rive/RiveReactNativeView.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/src/legacy/java/com/rive/RiveReactNativeView.kt b/android/src/legacy/java/com/rive/RiveReactNativeView.kt index 3c27882a..7a3863d5 100644 --- a/android/src/legacy/java/com/rive/RiveReactNativeView.kt +++ b/android/src/legacy/java/com/rive/RiveReactNativeView.kt @@ -184,6 +184,8 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) { @Volatile private var lastKnownViewModelInstance: ViewModelInstance? = null + private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper()) + private fun readViewModelInstanceOnMain(): ViewModelInstance? { // A refresh posted before dispose() can run after it; reading the // torn-down controller's stale list here would re-cache a released @@ -202,7 +204,7 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) { lastKnownViewModelInstance = readViewModelInstanceOnMain() return lastKnownViewModelInstance } - android.os.Handler(android.os.Looper.getMainLooper()).post { + mainHandler.post { lastKnownViewModelInstance = readViewModelInstanceOnMain() } return lastKnownViewModelInstance