Skip to content

Commit fb86bf0

Browse files
committed
feat: add RiveLogger with configurable JS handler and deprecation warnings (#201)
## Summary - Adds `RiveLog` — a general-purpose logging system that surfaces native logs to JS via a configurable handler - Default handler uses `console.error/warn/log` so logs show in the RN console out of the box - On both platforms, hooks into the Rive SDK's pluggable `RiveLog.Logger` to unify C++ runtime logs (state machine, artboard, command queue diagnostics) through the JS handler - Adds `DeprecationWarning` utility that emits once-per-session warnings when deprecated blocking methods are called ## Test plan - [ ] Build exerciser on iOS + Android - [ ] Call a deprecated method (e.g. `riveFile.viewModelByName(...)`) — verify warning in RN console - [ ] Call same method again — verify no duplicate warning - [ ] `RiveLog.setHandler(() => {})` — verify all logs suppressed - [ ] `RiveLog.resetHandler()` — verify back to default console output - [ ] Verify `onError` prop on RiveView still works independently
1 parent 359c540 commit fb86bf0

52 files changed

Lines changed: 1292 additions & 51 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.margelo.nitro.rive
2+
3+
object DeprecationWarning {
4+
private val warned = mutableSetOf<String>()
5+
6+
fun warn(method: String, replacement: String) {
7+
if (warned.add(method)) {
8+
RiveLog.w(
9+
"Deprecation",
10+
"'$method' is deprecated and blocks the calling thread. Use '$replacement' instead.",
11+
)
12+
}
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.margelo.nitro.rive
2+
3+
import androidx.annotation.Keep
4+
import com.facebook.proguard.annotations.DoNotStrip
5+
6+
@Keep
7+
@DoNotStrip
8+
class HybridRiveLogger : HybridRiveLoggerSpec() {
9+
override fun setHandler(handler: (level: String, tag: String, message: String) -> Unit) {
10+
RiveLog.handler = handler
11+
}
12+
13+
override fun resetHandler() {
14+
RiveLog.handler = null
15+
}
16+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.margelo.nitro.rive
2+
3+
import android.util.Log
4+
5+
object RiveLog {
6+
var handler: ((String, String, String) -> Unit)? = null
7+
8+
fun e(tag: String, message: String) {
9+
handler?.invoke("error", tag, message) ?: Log.e(tag, message)
10+
}
11+
12+
fun w(tag: String, message: String) {
13+
handler?.invoke("warn", tag, message) ?: Log.w(tag, message)
14+
}
15+
16+
fun i(tag: String, message: String) {
17+
handler?.invoke("info", tag, message) ?: Log.i(tag, message)
18+
}
19+
20+
fun d(tag: String, message: String) {
21+
handler?.invoke("debug", tag, message) ?: Log.d(tag, message)
22+
}
23+
}

android/src/new/java/com/margelo/nitro/rive/ExperimentalAssetLoader.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ object ExperimentalAssetLoader {
134134
} else if (data.matchesAt(8, 0x57, 0x45, 0x42, 0x50)) {
135135
AssetType.IMAGE // WebP (WEBP)
136136
} else {
137-
Log.w(TAG, "Unknown RIFF asset, assuming IMAGE. Declare asset type explicitly to avoid this.")
137+
RiveLog.w(TAG, "Unknown RIFF asset, assuming IMAGE. Declare asset type explicitly to avoid this.")
138138
AssetType.IMAGE
139139
}
140140
else -> {
141-
Log.w(TAG, "Could not infer asset type from magic bytes, assuming IMAGE. Declare asset type explicitly to avoid this.")
141+
RiveLog.w(TAG, "Could not infer asset type from magic bytes, assuming IMAGE. Declare asset type explicitly to avoid this.")
142142
AssetType.IMAGE
143143
}
144144
}

android/src/new/java/com/margelo/nitro/rive/HybridRiveFile.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.margelo.nitro.rive
22

3-
import android.util.Log
43
import androidx.annotation.Keep
54
import app.rive.Artboard
65
import app.rive.RiveFile
@@ -26,11 +25,12 @@ class HybridRiveFile(
2625
// Deprecated: Use getViewModelNamesAsync instead
2726
override val viewModelCount: Double?
2827
get() {
28+
DeprecationWarning.warn("viewModelCount", "getViewModelNamesAsync")
2929
val file = riveFile ?: return null
3030
return try {
3131
runBlocking { file.getViewModelNames() }.size.toDouble()
3232
} catch (e: Exception) {
33-
Log.e(TAG, "viewModelCount failed", e)
33+
RiveLog.e(TAG, "viewModelCount failed: ${e.message}")
3434
null
3535
}
3636
}
@@ -44,14 +44,15 @@ class HybridRiveFile(
4444

4545
// Deprecated: Use getViewModelNamesAsync + viewModelByNameAsync instead
4646
override fun viewModelByIndex(index: Double): HybridViewModelSpec? {
47+
DeprecationWarning.warn("viewModelByIndex", "getViewModelNamesAsync + viewModelByNameAsync")
4748
val file = riveFile ?: return null
4849
return try {
4950
val names = runBlocking { file.getViewModelNames() }
5051
val idx = index.toInt()
5152
if (idx < 0 || idx >= names.size) return null
5253
HybridViewModel(file, riveWorker, names[idx], this, ViewModelSource.Named(names[idx]))
5354
} catch (e: Exception) {
54-
Log.e(TAG, "viewModelByIndex($index) failed", e)
55+
RiveLog.e(TAG, "viewModelByIndex($index) failed: ${e.message}")
5556
null
5657
}
5758
}
@@ -67,10 +68,11 @@ class HybridRiveFile(
6768

6869
// Deprecated: Use viewModelByNameAsync instead
6970
override fun viewModelByName(name: String): HybridViewModelSpec? {
71+
DeprecationWarning.warn("viewModelByName", "viewModelByNameAsync")
7072
return try {
7173
runBlocking { viewModelByNameImpl(name, validate = true) }
7274
} catch (e: Exception) {
73-
Log.e(TAG, "viewModelByName('$name') failed", e)
75+
RiveLog.e(TAG, "viewModelByName('$name') failed: ${e.message}")
7476
null
7577
}
7678
}
@@ -105,10 +107,11 @@ class HybridRiveFile(
105107

106108
// Deprecated: Use defaultArtboardViewModelAsync instead
107109
override fun defaultArtboardViewModel(artboardBy: ArtboardBy?): HybridViewModelSpec? {
110+
DeprecationWarning.warn("defaultArtboardViewModel", "defaultArtboardViewModelAsync")
108111
return try {
109112
runBlocking { defaultArtboardViewModelImpl(artboardBy) }
110113
} catch (e: Exception) {
111-
Log.e(TAG, "defaultArtboardViewModel failed", e)
114+
RiveLog.e(TAG, "defaultArtboardViewModel failed: ${e.message}")
112115
null
113116
}
114117
}
@@ -120,11 +123,12 @@ class HybridRiveFile(
120123
// Deprecated: Use getArtboardCountAsync instead
121124
override val artboardCount: Double
122125
get() {
126+
DeprecationWarning.warn("artboardCount", "getArtboardCountAsync")
123127
val file = riveFile ?: return 0.0
124128
return try {
125129
runBlocking { file.getArtboardNames() }.size.toDouble()
126130
} catch (e: Exception) {
127-
Log.e(TAG, "artboardCount failed", e)
131+
RiveLog.e(TAG, "artboardCount failed: ${e.message}")
128132
0.0
129133
}
130134
}
@@ -139,11 +143,12 @@ class HybridRiveFile(
139143
// Deprecated: Use getArtboardNamesAsync instead
140144
override val artboardNames: Array<String>
141145
get() {
146+
DeprecationWarning.warn("artboardNames", "getArtboardNamesAsync")
142147
val file = riveFile ?: return emptyArray()
143148
return try {
144149
runBlocking { file.getArtboardNames() }.toTypedArray()
145150
} catch (e: Exception) {
146-
Log.e(TAG, "artboardNames failed", e)
151+
RiveLog.e(TAG, "artboardNames failed: ${e.message}")
147152
emptyArray()
148153
}
149154
}

android/src/new/java/com/margelo/nitro/rive/HybridRiveFileFactory.kt

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ import kotlinx.coroutines.Dispatchers
1616
import kotlinx.coroutines.withContext
1717

1818
/**
19-
* Custom RiveLog logger that logs to Logcat and broadcasts error messages
20-
* to registered listeners. This captures C++ errors from the Rive CommandQueue
21-
* (e.g., "State machine not found", "Draw failed") that are otherwise silent.
19+
* Custom RiveLog logger that routes all Rive C++ runtime logs through [RiveLog]
20+
* and broadcasts error messages to registered listeners. This captures C++ errors
21+
* from the Rive CommandQueue (e.g., "State machine not found", "Draw failed").
2222
*/
2323
object RiveErrorLogger : app.rive.RiveLog.Logger {
24-
private val logcat = app.rive.RiveLog.LogcatLogger()
2524
private val listeners = mutableListOf<(String) -> Unit>()
2625
private val reportedErrors = mutableSetOf<String>()
2726

@@ -47,13 +46,21 @@ object RiveErrorLogger : app.rive.RiveLog.Logger {
4746
synchronized(reportedErrors) { reportedErrors.clear() }
4847
}
4948

50-
override fun v(tag: String, msg: () -> String) = logcat.v(tag, msg)
51-
override fun d(tag: String, msg: () -> String) = logcat.d(tag, msg)
52-
override fun i(tag: String, msg: () -> String) = logcat.i(tag, msg)
53-
override fun w(tag: String, msg: () -> String) = logcat.w(tag, msg)
49+
override fun v(tag: String, msg: () -> String) {
50+
RiveLog.d(tag, msg())
51+
}
52+
override fun d(tag: String, msg: () -> String) {
53+
RiveLog.d(tag, msg())
54+
}
55+
override fun i(tag: String, msg: () -> String) {
56+
RiveLog.i(tag, msg())
57+
}
58+
override fun w(tag: String, msg: () -> String) {
59+
RiveLog.w(tag, msg())
60+
}
5461
override fun e(tag: String, t: Throwable?, msg: () -> String) {
5562
val message = msg()
56-
logcat.e(tag, t) { message }
63+
RiveLog.e(tag, message)
5764
broadcastError(tag, message)
5865
}
5966
}

android/src/new/java/com/margelo/nitro/rive/HybridViewModel.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.margelo.nitro.rive
22

3-
import android.util.Log
43
import androidx.annotation.Keep
54
import app.rive.RiveFile
65
import app.rive.ViewModelInstance
@@ -33,22 +32,24 @@ class HybridViewModel(
3332

3433
override val propertyCount: Double
3534
get() {
35+
DeprecationWarning.warn("propertyCount", "getPropertyCountAsync")
3636
val name = viewModelName ?: throw UnsupportedOperationException(NO_NAME_ERROR)
3737
return try {
3838
runBlocking { riveFile.getViewModelProperties(name) }.size.toDouble()
3939
} catch (e: Exception) {
40-
Log.e(TAG, "propertyCount failed", e)
40+
RiveLog.e(TAG, "propertyCount failed: ${e.message}")
4141
0.0
4242
}
4343
}
4444

4545
override val instanceCount: Double
4646
get() {
47+
DeprecationWarning.warn("instanceCount", "getInstanceCountAsync")
4748
val name = viewModelName ?: throw UnsupportedOperationException(NO_NAME_ERROR)
4849
return try {
4950
runBlocking { riveFile.getViewModelInstanceNames(name) }.size.toDouble()
5051
} catch (e: Exception) {
51-
Log.e(TAG, "instanceCount failed", e)
52+
RiveLog.e(TAG, "instanceCount failed: ${e.message}")
5253
0.0
5354
}
5455
}
@@ -68,6 +69,7 @@ class HybridViewModel(
6869

6970
// Deprecated: Use createInstanceByNameAsync instead
7071
override fun createInstanceByIndex(index: Double): HybridViewModelInstanceSpec? {
72+
DeprecationWarning.warn("createInstanceByIndex", "createInstanceByNameAsync")
7173
val name = viewModelName ?: throw UnsupportedOperationException(NO_NAME_ERROR)
7274
return try {
7375
val idx = index.toInt()
@@ -78,7 +80,7 @@ class HybridViewModel(
7880
} catch (e: UnsupportedOperationException) {
7981
throw e
8082
} catch (e: Exception) {
81-
Log.e(TAG, "createInstanceByIndex($index) failed", e)
83+
RiveLog.e(TAG, "createInstanceByIndex($index) failed: ${e.message}")
8284
null
8385
}
8486
}
@@ -94,13 +96,14 @@ class HybridViewModel(
9496

9597
// Deprecated: Use createInstanceByNameAsync instead
9698
override fun createInstanceByName(name: String): HybridViewModelInstanceSpec? {
99+
DeprecationWarning.warn("createInstanceByName", "createInstanceByNameAsync")
97100
if (viewModelName == null) throw UnsupportedOperationException(NO_NAME_ERROR)
98101
return try {
99102
runBlocking { createInstanceByNameImpl(name) }
100103
} catch (e: UnsupportedOperationException) {
101104
throw e
102105
} catch (e: Exception) {
103-
Log.e(TAG, "createInstanceByName('$name') failed", e)
106+
RiveLog.e(TAG, "createInstanceByName('$name') failed: ${e.message}")
104107
null
105108
}
106109
}
@@ -112,12 +115,13 @@ class HybridViewModel(
112115

113116
// Deprecated: Use createDefaultInstanceAsync instead
114117
override fun createDefaultInstance(): HybridViewModelInstanceSpec? {
118+
DeprecationWarning.warn("createDefaultInstance", "createDefaultInstanceAsync")
115119
return try {
116120
val source = vmSource.defaultInstance()
117121
val vmi = ViewModelInstance.fromFile(riveFile, source)
118122
HybridViewModelInstance(vmi, riveWorker, parentFile, viewModelName)
119123
} catch (e: Exception) {
120-
Log.e(TAG, "createDefaultInstance failed", e)
124+
RiveLog.e(TAG, "createDefaultInstance failed: ${e.message}")
121125
null
122126
}
123127
}
@@ -132,12 +136,13 @@ class HybridViewModel(
132136

133137
// Deprecated: Use createBlankInstanceAsync instead
134138
override fun createInstance(): HybridViewModelInstanceSpec? {
139+
DeprecationWarning.warn("createInstance", "createBlankInstanceAsync")
135140
return try {
136141
val source = vmSource.blankInstance()
137142
val vmi = ViewModelInstance.fromFile(riveFile, source)
138143
HybridViewModelInstance(vmi, riveWorker, parentFile, viewModelName)
139144
} catch (e: Exception) {
140-
Log.e(TAG, "createInstance (blank) failed", e)
145+
RiveLog.e(TAG, "createInstance (blank) failed: ${e.message}")
141146
null
142147
}
143148
}

android/src/new/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.margelo.nitro.rive
22

3-
import android.util.Log
43
import androidx.annotation.Keep
54
import app.rive.ViewModelInstance
65
import com.facebook.proguard.annotations.DoNotStrip
@@ -22,10 +21,11 @@ class HybridViewModelBooleanProperty(
2221
// Deprecated: Use getValueAsync (read) or set(value) (write) instead
2322
override var value: Boolean
2423
get() {
24+
DeprecationWarning.warn("BooleanProperty.value", "getValueAsync")
2525
return try {
2626
runBlocking { instance.getBooleanFlow(path).first() }
2727
} catch (e: Exception) {
28-
Log.e(TAG, "getValue failed for path '$path'", e)
28+
RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}")
2929
false
3030
}
3131
}

android/src/new/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.margelo.nitro.rive
22

3-
import android.util.Log
43
import androidx.annotation.Keep
54
import app.rive.ViewModelInstance
65
import com.facebook.proguard.annotations.DoNotStrip
@@ -22,10 +21,11 @@ class HybridViewModelColorProperty(
2221
// Deprecated: Use getValueAsync (read) or set(value) (write) instead
2322
override var value: Double
2423
get() {
24+
DeprecationWarning.warn("ColorProperty.value", "getValueAsync")
2525
return try {
2626
runBlocking { instance.getColorFlow(path).first() }.toDouble()
2727
} catch (e: Exception) {
28-
Log.e(TAG, "getValue failed for path '$path'", e)
28+
RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}")
2929
0.0
3030
}
3131
}

android/src/new/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.margelo.nitro.rive
22

3-
import android.util.Log
43
import androidx.annotation.Keep
54
import app.rive.ViewModelInstance
65
import com.facebook.proguard.annotations.DoNotStrip
@@ -22,10 +21,11 @@ class HybridViewModelEnumProperty(
2221
// Deprecated: Use getValueAsync (read) or set(value) (write) instead
2322
override var value: String
2423
get() {
24+
DeprecationWarning.warn("EnumProperty.value", "getValueAsync")
2525
return try {
2626
runBlocking { instance.getEnumFlow(path).first() }
2727
} catch (e: Exception) {
28-
Log.e(TAG, "getValue failed for path '$path'", e)
28+
RiveLog.e(TAG, "getValue failed for path '$path': ${e.message}")
2929
""
3030
}
3131
}

0 commit comments

Comments
 (0)