From ea72220859fd33f1d29c32389c58aa0a8c3a5b18 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Thu, 28 Sep 2023 14:53:48 +0200 Subject: [PATCH 1/3] fix: controller refcount out of sync with view --- .../rivereactnative/RiveReactNativeView.kt | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt b/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt index 25a48d9..5f13727 100644 --- a/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt +++ b/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt @@ -3,6 +3,8 @@ package com.rivereactnative import android.widget.FrameLayout import app.rive.runtime.kotlin.PointerEvents import app.rive.runtime.kotlin.RiveAnimationView +import app.rive.runtime.kotlin.controllers.ControllerState +import app.rive.runtime.kotlin.controllers.ControllerStateManagement import app.rive.runtime.kotlin.controllers.RiveFileController import app.rive.runtime.kotlin.core.* import app.rive.runtime.kotlin.core.errors.* @@ -23,7 +25,7 @@ import java.io.UnsupportedEncodingException class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout(context) { - private var riveAnimationView: RiveAnimationView = RiveAnimationView(context) + private var riveAnimationView: RiveAnimationView private var resId: Int = -1 private var url: String? = null private var animationName: String? = null @@ -35,6 +37,8 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout private var shouldBeReloaded = true private var exceptionManager: ExceptionsManagerModule? = null private var isUserHandlingErrors = false + @OptIn(ControllerStateManagement::class) + private var controllerState: ControllerState? = null; enum class Events(private val mName: String) { PLAY("onPlay"), @@ -51,6 +55,8 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout } init { + riveAnimationView = RiveAnimationView(context) + val listener = object : RiveFileController.Listener { override fun notifyLoop(animation: PlayableInstance) { if (animation is LinearAnimationInstance) { @@ -108,6 +114,44 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout addView(riveAnimationView) } + @OptIn(ControllerStateManagement::class) + override fun onAttachedToWindow() { + // The below solves: https://github.com/rive-app/rive-react-native/issues/198 + // When the view is returned to, we reuse the view's resources. + // For example, navigating to a new page and returning, or a TabView. + controllerState?.let { + riveAnimationView.restoreControllerState(it); + // The controller refCount is a combination of the creation of the controller and the + // initialization of the RiveArtboardRenderer (which calls acquire on the controller). + + // This could be decoupled in future versions of the Android runtime, and this code + // may require updating. + // + // For now we're doing a safety check to only add an additional acquire if the refCount is 0. + // As the RiveFileController would automatically have incremented the refCount on instantiation, + // and decreased when navigating away. It's safe to assume it should not be 0 at this point as + // the view still exists. + if (riveAnimationView.controller.refCount == 0) { + riveAnimationView.controller.acquire(); + // Another approach would be to recreate the entire RiveAnimationView and call addView again. + // But this does not work great due to this issue: https://github.com/facebook/react-native/issues/17968 + // Additionally, it may not be the most optimal route as the entire view needs to be recreated + // including all of the attached resources. + } + + // Re-add the view + addView(riveAnimationView) + } + super.onAttachedToWindow() + } + + @OptIn(ControllerStateManagement::class) + override fun onDetachedFromWindow() { + controllerState = riveAnimationView?.saveControllerState(); + removeView(riveAnimationView) + super.onDetachedFromWindow() + } + fun onPlay(animationName: String, isStateMachine: Boolean = false) { val reactContext = context as ReactContext From b31ea17fc3df6fb155030bcc3f60bc2d24da48e0 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 3 Oct 2023 16:31:44 +0200 Subject: [PATCH 2/3] chore: bump version for release --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fc79646..6c5a0b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rive-react-native", - "version": "6.1.0", + "version": "6.1.1", "description": "Rive React Native", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -149,4 +149,4 @@ ] ] } -} +} \ No newline at end of file From df31494920aacb7af9d31a02638b17e0e6edb333 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Tue, 3 Oct 2023 18:12:37 +0200 Subject: [PATCH 3/3] fix: dispose controller state on view drop --- .../main/java/com/rivereactnative/RiveReactNativeView.kt | 6 ++++++ .../java/com/rivereactnative/RiveReactNativeViewManager.kt | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt b/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt index 5f13727..6e7de2e 100644 --- a/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt +++ b/android/src/main/java/com/rivereactnative/RiveReactNativeView.kt @@ -114,6 +114,12 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout addView(riveAnimationView) } + @OptIn(ControllerStateManagement::class) + fun dispose() { + removeView(riveAnimationView) + controllerState?.dispose(); + } + @OptIn(ControllerStateManagement::class) override fun onAttachedToWindow() { // The below solves: https://github.com/rive-app/rive-react-native/issues/198 diff --git a/android/src/main/java/com/rivereactnative/RiveReactNativeViewManager.kt b/android/src/main/java/com/rivereactnative/RiveReactNativeViewManager.kt index c0cbe9e..c730240 100644 --- a/android/src/main/java/com/rivereactnative/RiveReactNativeViewManager.kt +++ b/android/src/main/java/com/rivereactnative/RiveReactNativeViewManager.kt @@ -1,6 +1,5 @@ package com.rivereactnative -import android.util.Log import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.MapBuilder import com.facebook.react.uimanager.SimpleViewManager @@ -115,6 +114,11 @@ class RiveReactNativeViewManager : SimpleViewManager() { return RiveReactNativeView(reactContext) } + override fun onDropViewInstance(view: RiveReactNativeView) { + view.dispose(); + super.onDropViewInstance(view) + } + override fun onAfterUpdateTransaction(view: RiveReactNativeView) { super.onAfterUpdateTransaction(view) view.update()