Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "app.rive:rive-android:${riveAndroidVersion}"
// Same version as the lifecycle libraries rive-android depends on.
implementation "androidx.lifecycle:lifecycle-process:2.9.4"
implementation project(":react-native-nitro-modules")
}

Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package com.margelo.nitro.rive

import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Choreographer
import androidx.annotation.Keep
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ProcessLifecycleOwner
import app.rive.RiveFile
import app.rive.RiveFileSource
import app.rive.core.CommandQueue
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.ArrayBuffer
import com.margelo.nitro.core.Promise
import com.rive.RiveWorkerConfig
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
Expand Down Expand Up @@ -99,26 +104,58 @@ class HybridRiveFileFactory : HybridRiveFileFactorySpec() {
* The experimental Rive SDK's CommandQueue needs to be polled every frame
* to process responses from the C++ command server. Without polling,
* all suspend functions (like RiveFile.fromSource) hang indefinitely.
* Polls only while the app is visible, so a backgrounded app gets no frame
* callbacks (issue #413).
*/
private fun startPolling(worker: CommandQueue) {
if (pollingStarted) return
pollingStarted = true
Handler(Looper.getMainLooper()).post {
val callback = object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
try {
worker.pollMessages()
} catch (e: Exception) {
Log.e(TAG, "pollMessages error", e)
}
Choreographer.getInstance().postFrameCallback(this)
CoroutineScope(Dispatchers.Main).launch {
val processLifecycle = ProcessLifecycleOwner.get().lifecycle
// INITIALIZED means the app disabled ProcessLifecycleOwner's
// initializer, so it would never report RESUMED.
val lifecycle = if (processLifecycle.currentState == Lifecycle.State.INITIALIZED) {
AlwaysResumed.lifecycle
} else {
VisibleProcess.follow(processLifecycle)
}
while (true) {
try {
worker.beginPolling(lifecycle)
return@launch
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Log.e(TAG, "pollMessages error", e)
}
}
Choreographer.getInstance().postFrameCallback(callback)
}
}
}

private object AlwaysResumed : LifecycleOwner {
override val lifecycle = LifecycleRegistry(this).apply { currentState = Lifecycle.State.RESUMED }
}

// beginPolling() only polls while RESUMED, but a paused activity can still be
// visible and rendering (picture-in-picture), so report STARTED as RESUMED.
private object VisibleProcess : LifecycleOwner {
override val lifecycle = LifecycleRegistry(this)

fun follow(process: Lifecycle): Lifecycle {
process.addObserver(
LifecycleEventObserver { _, _ ->
lifecycle.currentState = if (process.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Lifecycle.State.RESUMED
} else {
Lifecycle.State.CREATED
}
}
)
return lifecycle
}
}

private suspend fun buildRiveFile(
data: ByteArray,
referencedAssets: ReferencedAssetsType?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.annotation.Keep
import app.rive.Artboard
import app.rive.ViewModelInstance
import com.facebook.proguard.annotations.DoNotStrip
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand All @@ -23,6 +24,7 @@ class HybridViewModelArtboardProperty(
try {
val newArtboard = Artboard.fromFile(sourceFile, hybridArtboard.artboardName)
instance.setArtboard(path, newArtboard)
RiveReactNativeView.onViewModelChanged()
} catch (e: Exception) {
Log.e(TAG, "Failed to set artboard for path '$path'", e)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand Down Expand Up @@ -35,6 +36,7 @@ class HybridViewModelBooleanProperty(

override fun set(value: Boolean) {
instance.setBoolean(path, value)
RiveReactNativeView.onViewModelChanged()
}

override fun setValueAsync(value: Boolean): Promise<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand Down Expand Up @@ -35,6 +36,7 @@ class HybridViewModelColorProperty(

override fun set(value: Double) {
instance.setColor(path, value.toLong().toInt())
RiveReactNativeView.onViewModelChanged()
}

override fun setValueAsync(value: Double): Promise<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand Down Expand Up @@ -35,6 +36,7 @@ class HybridViewModelEnumProperty(

override fun set(value: String) {
instance.setEnum(path, value)
RiveReactNativeView.onViewModelChanged()
}

override fun setValueAsync(value: String): Promise<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand All @@ -31,6 +32,7 @@ class HybridViewModelImageProperty(
val result = ImageAsset.fromBytes(riveWorker, hybridImage.rawData)
if (result is app.rive.Result.Success) {
instance.setImage(path, result.value)
RiveReactNativeView.onViewModelChanged()
} else {
Log.e(TAG, "Failed to decode image for path '$path'")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import app.rive.core.CommandQueue
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.runBlocking
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand Down Expand Up @@ -77,13 +78,15 @@ class HybridViewModelListProperty(
DeprecationWarning.warn("ListProperty.addInstance", "addInstanceAsync")
val hybridInstance = instance as? HybridViewModelInstance ?: return
this.instance.appendToList(path, hybridInstance.viewModelInstance)
RiveReactNativeView.onViewModelChanged()
}

override fun addInstanceAt(instance: HybridViewModelInstanceSpec, index: Double): Boolean {
DeprecationWarning.warn("ListProperty.addInstanceAt", "addInstanceAtAsync")
val hybridInstance = instance as? HybridViewModelInstance ?: return false
return try {
this.instance.insertToListAtIndex(path, index.toInt(), hybridInstance.viewModelInstance)
RiveReactNativeView.onViewModelChanged()
true
} catch (e: Exception) {
Log.e(TAG, "addInstanceAt failed", e)
Expand All @@ -95,17 +98,20 @@ class HybridViewModelListProperty(
DeprecationWarning.warn("ListProperty.removeInstance", "removeInstanceAsync")
val hybridInstance = instance as? HybridViewModelInstance ?: return
this.instance.removeFromList(path, hybridInstance.viewModelInstance)
RiveReactNativeView.onViewModelChanged()
}

override fun removeInstanceAt(index: Double) {
DeprecationWarning.warn("ListProperty.removeInstanceAt", "removeInstanceAtAsync")
this.instance.removeFromListAtIndex(path, index.toInt())
RiveReactNativeView.onViewModelChanged()
}

override fun swap(index1: Double, index2: Double): Boolean {
DeprecationWarning.warn("ListProperty.swap", "swapAsync")
return try {
this.instance.swapListItems(path, index1.toInt(), index2.toInt())
RiveReactNativeView.onViewModelChanged()
true
} catch (e: Exception) {
Log.e(TAG, "swap failed", e)
Expand All @@ -118,6 +124,7 @@ class HybridViewModelListProperty(
?: return Promise.rejected(RuntimeException("Expected HybridViewModelInstance"))
return Promise.async {
this.instance.appendToList(path, hybridInstance.viewModelInstance)
RiveReactNativeView.onViewModelChanged()
}
}

Expand All @@ -127,6 +134,7 @@ class HybridViewModelListProperty(
return Promise.async {
requireIndexInBounds(index.toInt(), allowEnd = true)
this.instance.insertToListAtIndex(path, index.toInt(), hybridInstance.viewModelInstance)
RiveReactNativeView.onViewModelChanged()
}
}

Expand All @@ -135,13 +143,15 @@ class HybridViewModelListProperty(
?: return Promise.rejected(RuntimeException("Expected HybridViewModelInstance"))
return Promise.async {
this.instance.removeFromList(path, hybridInstance.viewModelInstance)
RiveReactNativeView.onViewModelChanged()
}
}

override fun removeInstanceAtAsync(index: Double): Promise<Unit> {
return Promise.async {
requireIndexInBounds(index.toInt())
this.instance.removeFromListAtIndex(path, index.toInt())
RiveReactNativeView.onViewModelChanged()
}
}

Expand All @@ -150,6 +160,7 @@ class HybridViewModelListProperty(
requireIndexInBounds(index1.toInt())
requireIndexInBounds(index2.toInt())
this.instance.swapListItems(path, index1.toInt(), index2.toInt())
RiveReactNativeView.onViewModelChanged()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand Down Expand Up @@ -35,6 +36,7 @@ class HybridViewModelNumberProperty(

override fun set(value: Double) {
instance.setNumber(path, value.toFloat())
RiveReactNativeView.onViewModelChanged()
}

override fun setValueAsync(value: Double): Promise<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand Down Expand Up @@ -35,6 +36,7 @@ class HybridViewModelStringProperty(

override fun set(value: String) {
instance.setString(path, value)
RiveReactNativeView.onViewModelChanged()
}

override fun setValueAsync(value: String): Promise<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.margelo.nitro.rive
import androidx.annotation.Keep
import app.rive.ViewModelInstance
import com.facebook.proguard.annotations.DoNotStrip
import com.rive.RiveReactNativeView

@Keep
@DoNotStrip
Expand All @@ -14,6 +15,7 @@ class HybridViewModelTriggerProperty(

override fun trigger() {
instance.fireTrigger(path)
RiveReactNativeView.onViewModelChanged()
}

override fun addListener(onChanged: () -> Unit): () -> Unit {
Expand Down
Loading
Loading