@rive-app/react-native 0.1.1-beta.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +18 -7
  2. package/android/src/main/java/com/margelo/nitro/rive/BaseHybridViewModelProperty.kt +9 -7
  3. package/android/src/main/java/com/margelo/nitro/rive/BaseHybridViewModelPropertyImpl.kt +43 -24
  4. package/android/src/main/java/com/margelo/nitro/rive/HybridRiveFile.kt +0 -1
  5. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt +3 -2
  6. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt +3 -2
  7. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt +3 -2
  8. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelImageProperty.kt +3 -2
  9. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelInstance.kt +26 -47
  10. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt +64 -0
  11. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt +3 -2
  12. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt +3 -2
  13. package/android/src/main/java/com/margelo/nitro/rive/HybridViewModelTriggerProperty.kt +3 -2
  14. package/ios/BaseHybridViewModelProperty.swift +22 -6
  15. package/ios/HybridViewModel.swift +1 -6
  16. package/ios/HybridViewModelBooleanProperty.swift +1 -9
  17. package/ios/HybridViewModelColorProperty.swift +3 -12
  18. package/ios/HybridViewModelEnumProperty.swift +1 -9
  19. package/ios/HybridViewModelImageProperty.swift +4 -4
  20. package/ios/HybridViewModelInstance.swift +6 -6
  21. package/ios/HybridViewModelListProperty.swift +62 -0
  22. package/ios/HybridViewModelNumberProperty.swift +2 -13
  23. package/ios/HybridViewModelStringProperty.swift +1 -9
  24. package/ios/HybridViewModelTriggerProperty.swift +5 -14
  25. package/ios/RiveReactNativeView.swift +36 -0
  26. package/lib/module/hooks/useRiveColor.js +0 -1
  27. package/lib/module/hooks/useRiveColor.js.map +1 -1
  28. package/lib/module/hooks/useRiveList.js +71 -0
  29. package/lib/module/hooks/useRiveList.js.map +1 -0
  30. package/lib/module/hooks/useRiveProperty.js +6 -12
  31. package/lib/module/hooks/useRiveProperty.js.map +1 -1
  32. package/lib/module/hooks/useViewModelInstance.js +139 -0
  33. package/lib/module/hooks/useViewModelInstance.js.map +1 -0
  34. package/lib/module/index.js +2 -0
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/typescript/src/hooks/useRiveColor.d.ts +6 -4
  37. package/lib/typescript/src/hooks/useRiveColor.d.ts.map +1 -1
  38. package/lib/typescript/src/hooks/useRiveList.d.ts +11 -0
  39. package/lib/typescript/src/hooks/useRiveList.d.ts.map +1 -0
  40. package/lib/typescript/src/hooks/useRiveProperty.d.ts +6 -1
  41. package/lib/typescript/src/hooks/useRiveProperty.d.ts.map +1 -1
  42. package/lib/typescript/src/hooks/useViewModelInstance.d.ts +86 -0
  43. package/lib/typescript/src/hooks/useViewModelInstance.d.ts.map +1 -0
  44. package/lib/typescript/src/index.d.ts +4 -1
  45. package/lib/typescript/src/index.d.ts.map +1 -1
  46. package/lib/typescript/src/specs/ViewModel.nitro.d.ts +39 -15
  47. package/lib/typescript/src/specs/ViewModel.nitro.d.ts.map +1 -1
  48. package/lib/typescript/src/types.d.ts +47 -3
  49. package/lib/typescript/src/types.d.ts.map +1 -1
  50. package/nitrogen/generated/android/c++/JHybridViewModelBooleanPropertySpec.cpp +14 -4
  51. package/nitrogen/generated/android/c++/JHybridViewModelBooleanPropertySpec.hpp +1 -1
  52. package/nitrogen/generated/android/c++/JHybridViewModelColorPropertySpec.cpp +14 -4
  53. package/nitrogen/generated/android/c++/JHybridViewModelColorPropertySpec.hpp +1 -1
  54. package/nitrogen/generated/android/c++/JHybridViewModelEnumPropertySpec.cpp +14 -4
  55. package/nitrogen/generated/android/c++/JHybridViewModelEnumPropertySpec.hpp +1 -1
  56. package/nitrogen/generated/android/c++/JHybridViewModelImagePropertySpec.cpp +15 -6
  57. package/nitrogen/generated/android/c++/JHybridViewModelImagePropertySpec.hpp +1 -1
  58. package/nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.cpp +9 -0
  59. package/nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.hpp +1 -0
  60. package/nitrogen/generated/android/c++/JHybridViewModelListPropertySpec.cpp +102 -0
  61. package/nitrogen/generated/android/c++/JHybridViewModelListPropertySpec.hpp +73 -0
  62. package/nitrogen/generated/android/c++/JHybridViewModelNumberPropertySpec.cpp +14 -4
  63. package/nitrogen/generated/android/c++/JHybridViewModelNumberPropertySpec.hpp +1 -1
  64. package/nitrogen/generated/android/c++/JHybridViewModelStringPropertySpec.cpp +14 -4
  65. package/nitrogen/generated/android/c++/JHybridViewModelStringPropertySpec.hpp +1 -1
  66. package/nitrogen/generated/android/c++/JHybridViewModelTriggerPropertySpec.cpp +12 -3
  67. package/nitrogen/generated/android/c++/JHybridViewModelTriggerPropertySpec.hpp +1 -1
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelBooleanPropertySpec.kt +3 -3
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelColorPropertySpec.kt +3 -3
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelEnumPropertySpec.kt +3 -3
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelImagePropertySpec.kt +3 -3
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelInstanceSpec.kt +4 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelListPropertySpec.kt +92 -0
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelNumberPropertySpec.kt +3 -3
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelStringPropertySpec.kt +3 -3
  76. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rive/HybridViewModelTriggerPropertySpec.kt +3 -3
  77. package/nitrogen/generated/android/rive+autolinking.cmake +2 -0
  78. package/nitrogen/generated/android/riveOnLoad.cpp +4 -74
  79. package/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.cpp +17 -0
  80. package/nitrogen/generated/ios/RNRive-Swift-Cxx-Bridge.hpp +53 -0
  81. package/nitrogen/generated/ios/RNRive-Swift-Cxx-Umbrella.hpp +5 -0
  82. package/nitrogen/generated/ios/RNRiveAutolinking.mm +0 -72
  83. package/nitrogen/generated/ios/RNRiveAutolinking.swift +0 -135
  84. package/nitrogen/generated/ios/c++/HybridViewModelBooleanPropertySpecSwift.hpp +3 -1
  85. package/nitrogen/generated/ios/c++/HybridViewModelColorPropertySpecSwift.hpp +3 -1
  86. package/nitrogen/generated/ios/c++/HybridViewModelEnumPropertySpecSwift.hpp +3 -1
  87. package/nitrogen/generated/ios/c++/HybridViewModelImagePropertySpecSwift.hpp +3 -1
  88. package/nitrogen/generated/ios/c++/HybridViewModelInstanceSpecSwift.hpp +11 -0
  89. package/nitrogen/generated/ios/c++/HybridViewModelListPropertySpecSwift.cpp +11 -0
  90. package/nitrogen/generated/ios/c++/HybridViewModelListPropertySpecSwift.hpp +134 -0
  91. package/nitrogen/generated/ios/c++/HybridViewModelNumberPropertySpecSwift.hpp +3 -1
  92. package/nitrogen/generated/ios/c++/HybridViewModelStringPropertySpecSwift.hpp +3 -1
  93. package/nitrogen/generated/ios/c++/HybridViewModelTriggerPropertySpecSwift.hpp +3 -1
  94. package/nitrogen/generated/ios/swift/HybridViewModelBooleanPropertySpec.swift +1 -1
  95. package/nitrogen/generated/ios/swift/HybridViewModelBooleanPropertySpec_cxx.swift +8 -4
  96. package/nitrogen/generated/ios/swift/HybridViewModelColorPropertySpec.swift +1 -1
  97. package/nitrogen/generated/ios/swift/HybridViewModelColorPropertySpec_cxx.swift +8 -4
  98. package/nitrogen/generated/ios/swift/HybridViewModelEnumPropertySpec.swift +1 -1
  99. package/nitrogen/generated/ios/swift/HybridViewModelEnumPropertySpec_cxx.swift +8 -4
  100. package/nitrogen/generated/ios/swift/HybridViewModelImagePropertySpec.swift +1 -1
  101. package/nitrogen/generated/ios/swift/HybridViewModelImagePropertySpec_cxx.swift +8 -4
  102. package/nitrogen/generated/ios/swift/HybridViewModelInstanceSpec.swift +1 -0
  103. package/nitrogen/generated/ios/swift/HybridViewModelInstanceSpec_cxx.swift +21 -0
  104. package/nitrogen/generated/ios/swift/HybridViewModelListPropertySpec.swift +63 -0
  105. package/nitrogen/generated/ios/swift/HybridViewModelListPropertySpec_cxx.swift +248 -0
  106. package/nitrogen/generated/ios/swift/HybridViewModelNumberPropertySpec.swift +1 -1
  107. package/nitrogen/generated/ios/swift/HybridViewModelNumberPropertySpec_cxx.swift +8 -4
  108. package/nitrogen/generated/ios/swift/HybridViewModelStringPropertySpec.swift +1 -1
  109. package/nitrogen/generated/ios/swift/HybridViewModelStringPropertySpec_cxx.swift +8 -4
  110. package/nitrogen/generated/ios/swift/HybridViewModelTriggerPropertySpec.swift +1 -1
  111. package/nitrogen/generated/ios/swift/HybridViewModelTriggerPropertySpec_cxx.swift +8 -4
  112. package/nitrogen/generated/shared/c++/HybridViewModelBooleanPropertySpec.hpp +1 -1
  113. package/nitrogen/generated/shared/c++/HybridViewModelColorPropertySpec.hpp +1 -1
  114. package/nitrogen/generated/shared/c++/HybridViewModelEnumPropertySpec.hpp +1 -1
  115. package/nitrogen/generated/shared/c++/HybridViewModelImagePropertySpec.hpp +1 -1
  116. package/nitrogen/generated/shared/c++/HybridViewModelInstanceSpec.cpp +1 -0
  117. package/nitrogen/generated/shared/c++/HybridViewModelInstanceSpec.hpp +4 -0
  118. package/nitrogen/generated/shared/c++/HybridViewModelListPropertySpec.cpp +30 -0
  119. package/nitrogen/generated/shared/c++/HybridViewModelListPropertySpec.hpp +76 -0
  120. package/nitrogen/generated/shared/c++/HybridViewModelNumberPropertySpec.hpp +1 -1
  121. package/nitrogen/generated/shared/c++/HybridViewModelStringPropertySpec.hpp +1 -1
  122. package/nitrogen/generated/shared/c++/HybridViewModelTriggerPropertySpec.hpp +1 -1
  123. package/package.json +3 -3
  124. package/src/hooks/useRiveColor.ts +7 -4
  125. package/src/hooks/useRiveList.ts +108 -0
  126. package/src/hooks/useRiveProperty.ts +20 -13
  127. package/src/hooks/useViewModelInstance.ts +195 -0
  128. package/src/index.tsx +4 -0
  129. package/src/specs/ViewModel.nitro.ts +43 -15
  130. package/src/types.tsx +58 -3
package/README.md CHANGED
@@ -1,10 +1,21 @@
1
1
  # @rive-app/react-native
2
2
 
3
- Rive React Native 2.0
3
+ [![Build](https://github.com/rive-app/rive-nitro-react-native/actions/workflows/ci.yml/badge.svg)](https://github.com/rive-app/rive-nitro-react-native/actions)
4
+ [![NPM Version](https://img.shields.io/npm/v/@rive-app/react-native)](https://www.npmjs.com/package/@rive-app/react-native)
5
+ [![Downloads](https://img.shields.io/npm/dm/@rive-app/react-native)](https://www.npmjs.com/package/@rive-app/react-native)
6
+ [![React Native](https://img.shields.io/badge/React_Native-0.78+-61dafb)](https://reactnative.dev/)
7
+ [![iOS](https://img.shields.io/badge/iOS-15.1+-blue)](#requirements)
8
+ [![Android](https://img.shields.io/badge/Android-7.0+-green)](#requirements)
9
+ [![Rive iOS Runtime](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Frive-app%2Frive-nitro-react-native%2Fmain%2Fpackage.json&query=%24.runtimeVersions.ios&label=Rive%20iOS%20Runtime&color=informational)](https://github.com/rive-app/rive-ios/releases)
10
+ [![Rive Android Runtime](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Frive-app%2Frive-nitro-react-native%2Fmain%2Fpackage.json&query=%24.runtimeVersions.android&label=Rive%20Android%20Runtime&color=informational)](https://github.com/rive-app/rive-android/releases)
4
11
 
5
- ## Development Preview
12
+ **Rive React Native 2.0**
6
13
 
7
- > **⚠️ Development Preview**: This package is currently in development preview. While it's functional and actively maintained, the API may change in future releases. We recommend testing thoroughly before using in production applications. We're actively gathering feedback to improve the library. Please share your thoughts and report any issues you encounter.
14
+ ![Rive hero image](https://cdn.rive.app/rive_logo_dark_bg.png)
15
+
16
+ ## Early Release
17
+
18
+ > **⚠️ Early Release**: This package is in active development. We recommend testing thoroughly before using in production applications. We're actively gathering feedback to improve the library. Please share your thoughts and report any issues you encounter.
8
19
 
9
20
  ## Requirements
10
21
 
@@ -23,7 +34,7 @@ Rive React Native 2.0
23
34
  ## Installation
24
35
 
25
36
  ```sh
26
- npm install rive-app/rive-nitro-react-native react-native-nitro-modules
37
+ npm install @rive-app/react-native react-native-nitro-modules
27
38
  ```
28
39
 
29
40
  > `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/).
@@ -117,6 +128,7 @@ export default {
117
128
  },
118
129
  };
119
130
  ```
131
+
120
132
  </details>
121
133
 
122
134
  ## Error Handling
@@ -237,9 +249,9 @@ This section tracks new features and improvements planned for this runtime that
237
249
  | Feature | Status |
238
250
  | ----------------------------------------------------------------------------------------------------- | ------ |
239
251
  | [Reusable .riv File resources (preloading)](https://github.com/rive-app/rive-react-native/issues/260) | ✅ |
240
- | [Data Binding - Images](https://github.com/rive-app/rive-nitro-react-native/issues/9) | 🚧 |
252
+ | [Data Binding - Images](https://github.com/rive-app/rive-nitro-react-native/issues/9) | |
241
253
  | [Data Binding - Artboards](https://github.com/rive-app/rive-nitro-react-native/issues/10) | 🚧 |
242
- | [Data Binding - Lists](https://github.com/rive-app/rive-nitro-react-native/issues/11) | 🚧 |
254
+ | [Data Binding - Lists](https://github.com/rive-app/rive-nitro-react-native/issues/11) | |
243
255
  | [Data Binding - Value props](https://github.com/rive-app/rive-nitro-react-native/pull/24) | 🚧 |
244
256
  | [Suspense](https://github.com/rive-app/rive-nitro-react-native/pull/19) | 🚧 |
245
257
 
@@ -254,4 +266,3 @@ MIT
254
266
  ---
255
267
 
256
268
  Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
257
-
@@ -9,12 +9,14 @@ import kotlinx.coroutines.flow.Flow
9
9
  @Keep
10
10
  @DoNotStrip
11
11
  interface BaseHybridViewModelProperty<T> {
12
- val scope: CoroutineScope?
13
- val job: Job?
14
- val listeners: MutableList<(T) -> Unit>
12
+ val scope: CoroutineScope?
13
+ val job: Job?
14
+ val listeners: MutableMap<String, (T) -> Unit>
15
15
 
16
- fun ensureValueListenerJob(valueFlow: Flow<T>, drop: Int = 0)
17
- fun onChanged(value: T)
18
- fun removeListeners()
19
- fun dispose()
16
+ fun ensureValueListenerJob(valueFlow: Flow<T>, drop: Int = 0)
17
+ fun onChanged(value: T)
18
+ fun addListenerInternal(callback: (T) -> Unit): () -> Unit
19
+ fun removeListener(id: String)
20
+ fun removeListeners()
21
+ fun dispose()
20
22
  }
@@ -9,42 +9,61 @@ import kotlinx.coroutines.cancel
9
9
  import kotlinx.coroutines.launch
10
10
  import kotlinx.coroutines.flow.Flow
11
11
  import kotlinx.coroutines.flow.drop
12
+ import java.lang.ref.WeakReference
13
+ import java.util.UUID
12
14
 
13
15
  @Keep
14
16
  @DoNotStrip
15
17
  class BaseHybridViewModelPropertyImpl<T> : BaseHybridViewModelProperty<T> {
16
- override var scope: CoroutineScope? = null
17
- override var job: Job? = null
18
- override val listeners = mutableListOf<(T) -> Unit>()
18
+ override var scope: CoroutineScope? = null
19
+ override var job: Job? = null
20
+ override val listeners = mutableMapOf<String, (T) -> Unit>()
19
21
 
20
22
  override fun ensureValueListenerJob(valueFlow: Flow<T>, drop: Int) {
21
- if (scope == null) {
22
- scope = CoroutineScope(Dispatchers.Default)
23
- }
24
- if (job == null) {
25
- job = scope?.launch {
26
- valueFlow.drop(drop).collect { value ->
27
- onChanged(value)
28
- }
29
- }
23
+ if (scope == null) {
24
+ scope = CoroutineScope(Dispatchers.Default)
25
+ }
26
+ if (job == null) {
27
+ job = scope?.launch {
28
+ valueFlow.drop(drop).collect { value ->
29
+ onChanged(value)
30
30
  }
31
+ }
31
32
  }
33
+ }
32
34
 
33
- override fun onChanged(value: T) {
34
- listeners.forEach { listener ->
35
- listener(value)
36
- }
35
+ override fun onChanged(value: T) {
36
+ listeners.values.forEach { listener ->
37
+ listener(value)
37
38
  }
39
+ }
38
40
 
39
- override fun removeListeners() {
40
- listeners.clear()
41
- job?.cancel()
42
- scope?.cancel()
43
- job = null
44
- scope = null
41
+ override fun addListenerInternal(callback: (T) -> Unit): () -> Unit {
42
+ val id = UUID.randomUUID().toString()
43
+ listeners[id] = callback
44
+ val weakSelf = WeakReference(this)
45
+ return {
46
+ weakSelf.get()?.removeListener(id)
45
47
  }
48
+ }
46
49
 
47
- override fun dispose() {
48
- removeListeners()
50
+ override fun removeListener(id: String) {
51
+ listeners.remove(id)
52
+ if (listeners.isEmpty()) {
53
+ job?.cancel()
54
+ job = null
49
55
  }
56
+ }
57
+
58
+ override fun removeListeners() {
59
+ listeners.clear()
60
+ job?.cancel()
61
+ scope?.cancel()
62
+ job = null
63
+ scope = null
64
+ }
65
+
66
+ override fun dispose() {
67
+ removeListeners()
68
+ }
50
69
  }
@@ -3,7 +3,6 @@ package com.margelo.nitro.rive
3
3
  import androidx.annotation.Keep
4
4
  import app.rive.runtime.kotlin.core.File
5
5
  import com.facebook.proguard.annotations.DoNotStrip
6
- import com.margelo.nitro.NitroModules
7
6
  import java.lang.ref.WeakReference
8
7
  import kotlinx.coroutines.CoroutineScope
9
8
  import kotlinx.coroutines.Dispatchers
@@ -15,8 +15,9 @@ class HybridViewModelBooleanProperty(private val viewModelBoolean: ViewModelBool
15
15
  viewModelBoolean.value = value
16
16
  }
17
17
 
18
- override fun addListener(onChanged: (value: Boolean) -> Unit) {
19
- listeners.add(onChanged)
18
+ override fun addListener(onChanged: (value: Boolean) -> Unit): () -> Unit {
19
+ val remover = addListenerInternal(onChanged)
20
20
  ensureValueListenerJob(viewModelBoolean.valueFlow)
21
+ return remover
21
22
  }
22
23
  }
@@ -15,8 +15,9 @@ class HybridViewModelColorProperty(private val viewModelColor: ViewModelColorPro
15
15
  viewModelColor.value = value.toInt()
16
16
  }
17
17
 
18
- override fun addListener(onChanged: (value: Double) -> Unit) {
19
- listeners.add { intValue: Int -> onChanged(intValue.toDouble()) }
18
+ override fun addListener(onChanged: (value: Double) -> Unit): () -> Unit {
19
+ val remover = addListenerInternal { intValue: Int -> onChanged(intValue.toDouble()) }
20
20
  ensureValueListenerJob(viewModelColor.valueFlow)
21
+ return remover
21
22
  }
22
23
  }
@@ -15,8 +15,9 @@ class HybridViewModelEnumProperty(private val viewModelEnum: ViewModelEnumProper
15
15
  viewModelEnum.value = value
16
16
  }
17
17
 
18
- override fun addListener(onChanged: (value: String) -> Unit) {
19
- listeners.add(onChanged)
18
+ override fun addListener(onChanged: (value: String) -> Unit): () -> Unit {
19
+ val remover = addListenerInternal(onChanged)
20
20
  ensureValueListenerJob(viewModelEnum.valueFlow)
21
+ return remover
21
22
  }
22
23
  }
@@ -14,8 +14,9 @@ class HybridViewModelImageProperty(private val viewModelImage: ViewModelImagePro
14
14
  viewModelImage.set((image as? HybridRiveImage)?.renderImage)
15
15
  }
16
16
 
17
- override fun addListener(onChanged: () -> Unit) {
18
- listeners.add { _ -> onChanged() }
17
+ override fun addListener(onChanged: () -> Unit): () -> Unit {
18
+ val remover = addListenerInternal { _ -> onChanged() }
19
19
  ensureValueListenerJob(viewModelImage.valueFlow.map { })
20
+ return remover
20
21
  }
21
22
  }
@@ -11,66 +11,45 @@ class HybridViewModelInstance(val viewModelInstance: ViewModelInstance) : Hybrid
11
11
  override val instanceName: String
12
12
  get() = viewModelInstance.name
13
13
 
14
- override fun numberProperty(path: String): HybridViewModelNumberPropertySpec? {
15
- try {
16
- val numberProper = viewModelInstance.getNumberProperty(path)
17
- return HybridViewModelNumberProperty(numberProper)
14
+ // Returns null if ViewModelException is thrown for iOS parity
15
+ // (iOS SDK returns nil when property not found, Android SDK throws)
16
+ private inline fun <T> getPropertyOrNull(block: () -> T): T? {
17
+ return try {
18
+ block()
18
19
  } catch (e: ViewModelException) {
19
- return null
20
+ null
20
21
  }
21
22
  }
22
23
 
23
- override fun stringProperty(path: String): HybridViewModelStringPropertySpec? {
24
- try {
25
- val stringProperty = viewModelInstance.getStringProperty(path)
26
- return HybridViewModelStringProperty(stringProperty)
27
- } catch (e: ViewModelException) {
28
- return null
29
- }
24
+ override fun numberProperty(path: String) = getPropertyOrNull {
25
+ HybridViewModelNumberProperty(viewModelInstance.getNumberProperty(path))
30
26
  }
31
27
 
32
- override fun booleanProperty(path: String): HybridViewModelBooleanPropertySpec? {
33
- try {
34
- val booleanProperty = viewModelInstance.getBooleanProperty(path)
35
- return HybridViewModelBooleanProperty(booleanProperty)
36
- } catch (e: ViewModelException) {
37
- return null
38
- }
28
+ override fun stringProperty(path: String) = getPropertyOrNull {
29
+ HybridViewModelStringProperty(viewModelInstance.getStringProperty(path))
39
30
  }
40
31
 
41
- override fun colorProperty(path: String): HybridViewModelColorPropertySpec? {
42
- try {
43
- val colorProperty = viewModelInstance.getColorProperty(path)
44
- return HybridViewModelColorProperty(colorProperty)
45
- } catch (e: ViewModelException) {
46
- return null
47
- }
32
+ override fun booleanProperty(path: String) = getPropertyOrNull {
33
+ HybridViewModelBooleanProperty(viewModelInstance.getBooleanProperty(path))
48
34
  }
49
35
 
50
- override fun enumProperty(path: String): HybridViewModelEnumPropertySpec? {
51
- try {
52
- val enumProperty = viewModelInstance.getEnumProperty(path)
53
- return HybridViewModelEnumProperty(enumProperty)
54
- } catch (e: ViewModelException) {
55
- return null
56
- }
36
+ override fun colorProperty(path: String) = getPropertyOrNull {
37
+ HybridViewModelColorProperty(viewModelInstance.getColorProperty(path))
57
38
  }
58
39
 
59
- override fun triggerProperty(path: String): HybridViewModelTriggerPropertySpec? {
60
- try {
61
- val triggerProperty = viewModelInstance.getTriggerProperty(path)
62
- return HybridViewModelTriggerProperty(triggerProperty)
63
- } catch (e: ViewModelException) {
64
- return null
65
- }
40
+ override fun enumProperty(path: String) = getPropertyOrNull {
41
+ HybridViewModelEnumProperty(viewModelInstance.getEnumProperty(path))
66
42
  }
67
43
 
68
- override fun imageProperty(path: String): HybridViewModelImagePropertySpec? {
69
- try {
70
- val imageProperty = viewModelInstance.getImageProperty(path)
71
- return HybridViewModelImageProperty(imageProperty)
72
- } catch (e: ViewModelException) {
73
- return null
74
- }
44
+ override fun triggerProperty(path: String) = getPropertyOrNull {
45
+ HybridViewModelTriggerProperty(viewModelInstance.getTriggerProperty(path))
46
+ }
47
+
48
+ override fun imageProperty(path: String) = getPropertyOrNull {
49
+ HybridViewModelImageProperty(viewModelInstance.getImageProperty(path))
50
+ }
51
+
52
+ override fun listProperty(path: String) = getPropertyOrNull {
53
+ HybridViewModelListProperty(viewModelInstance.getListProperty(path))
75
54
  }
76
55
  }
@@ -0,0 +1,64 @@
1
+ package com.margelo.nitro.rive
2
+
3
+ import androidx.annotation.Keep
4
+ import app.rive.runtime.kotlin.core.ViewModelListProperty
5
+ import com.facebook.proguard.annotations.DoNotStrip
6
+ import kotlinx.coroutines.flow.map
7
+
8
+ @Keep
9
+ @DoNotStrip
10
+ class HybridViewModelListProperty(private val listProperty: ViewModelListProperty) :
11
+ HybridViewModelListPropertySpec(),
12
+ BaseHybridViewModelProperty<Unit> by BaseHybridViewModelPropertyImpl() {
13
+ override val length: Double
14
+ get() = listProperty.size.toDouble()
15
+
16
+ private fun requireHybridInstance(instance: HybridViewModelInstanceSpec): HybridViewModelInstance {
17
+ return instance as? HybridViewModelInstance
18
+ ?: throw IllegalArgumentException("Expected HybridViewModelInstance but got ${instance::class.simpleName}")
19
+ }
20
+
21
+ override fun getInstanceAt(index: Double): HybridViewModelInstanceSpec? {
22
+ val idx = index.toInt()
23
+ if (idx < 0 || idx >= listProperty.size) return null
24
+ return HybridViewModelInstance(listProperty.elementAt(idx))
25
+ }
26
+
27
+ override fun addInstance(instance: HybridViewModelInstanceSpec) {
28
+ val hybridInstance = requireHybridInstance(instance)
29
+ listProperty.add(hybridInstance.viewModelInstance)
30
+ }
31
+
32
+ override fun addInstanceAt(instance: HybridViewModelInstanceSpec, index: Double): Boolean {
33
+ val hybridInstance = requireHybridInstance(instance)
34
+ val idx = index.toInt()
35
+ if (idx < 0 || idx > listProperty.size) return false
36
+ listProperty.add(idx, hybridInstance.viewModelInstance)
37
+ return true
38
+ }
39
+
40
+ override fun removeInstance(instance: HybridViewModelInstanceSpec) {
41
+ val hybridInstance = requireHybridInstance(instance)
42
+ listProperty.remove(hybridInstance.viewModelInstance)
43
+ }
44
+
45
+ override fun removeInstanceAt(index: Double) {
46
+ listProperty.removeAt(index.toInt())
47
+ }
48
+
49
+ override fun swap(index1: Double, index2: Double): Boolean {
50
+ val idx1 = index1.toInt()
51
+ val idx2 = index2.toInt()
52
+ if (idx1 < 0 || idx1 >= listProperty.size || idx2 < 0 || idx2 >= listProperty.size) {
53
+ return false
54
+ }
55
+ listProperty.swap(idx1, idx2)
56
+ return true
57
+ }
58
+
59
+ override fun addListener(onChanged: () -> Unit): () -> Unit {
60
+ val remover = addListenerInternal { _ -> onChanged() }
61
+ ensureValueListenerJob(listProperty.valueFlow.map { })
62
+ return remover
63
+ }
64
+ }
@@ -16,8 +16,9 @@ class HybridViewModelNumberProperty(private val viewModelNumber: ViewModelNumber
16
16
  viewModelNumber.value = value.toFloat()
17
17
  }
18
18
 
19
- override fun addListener(onChanged: (value: Double) -> Unit) {
20
- listeners.add(onChanged)
19
+ override fun addListener(onChanged: (value: Double) -> Unit): () -> Unit {
20
+ val remover = addListenerInternal(onChanged)
21
21
  ensureValueListenerJob(viewModelNumber.valueFlow.map { it.toDouble() })
22
+ return remover
22
23
  }
23
24
  }
@@ -15,8 +15,9 @@ class HybridViewModelStringProperty(private val viewModelString: ViewModelString
15
15
  viewModelString.value = value
16
16
  }
17
17
 
18
- override fun addListener(onChanged: (value: String) -> Unit) {
19
- listeners.add(onChanged)
18
+ override fun addListener(onChanged: (value: String) -> Unit): () -> Unit {
19
+ val remover = addListenerInternal(onChanged)
20
20
  ensureValueListenerJob(viewModelString.valueFlow)
21
+ return remover
21
22
  }
22
23
  }
@@ -13,9 +13,10 @@ class HybridViewModelTriggerProperty(private val viewModelTrigger: ViewModelTrig
13
13
  viewModelTrigger.trigger()
14
14
  }
15
15
 
16
- override fun addListener(onChanged: () -> Unit) {
17
- listeners.add { _ -> onChanged() }
16
+ override fun addListener(onChanged: () -> Unit): () -> Unit {
17
+ val remover = addListenerInternal { _ -> onChanged() }
18
18
  // We drop the first value as a trigger has no initial value
19
19
  ensureValueListenerJob(viewModelTrigger.valueFlow, 1)
20
+ return remover
20
21
  }
21
22
  }
@@ -23,6 +23,7 @@ typealias EnumPropertyType = RiveDataBindingViewModel.Instance.EnumProperty
23
23
  typealias ColorPropertyType = RiveDataBindingViewModel.Instance.ColorProperty
24
24
  typealias TriggerPropertyType = RiveDataBindingViewModel.Instance.TriggerProperty
25
25
  typealias ImagePropertyType = RiveDataBindingViewModel.Instance.ImageProperty
26
+ typealias ListPropertyType = RiveDataBindingViewModel.Instance.ListProperty
26
27
 
27
28
  // Make all Rive property types conform to the protocol
28
29
  extension BooleanPropertyType: RivePropertyWithListeners {
@@ -56,6 +57,14 @@ extension ImagePropertyType: RivePropertyWithListeners {
56
57
  }
57
58
  }
58
59
 
60
+ extension ListPropertyType: RivePropertyWithListeners {
61
+ typealias ListenerValueType = Void
62
+
63
+ func addListener(_ callback: @escaping ListenerType) -> UUID {
64
+ addListener { callback(()) }
65
+ }
66
+ }
67
+
59
68
  /// Helper class for managing ViewModel property listeners
60
69
  class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
61
70
  private var listenerIds: [UUID] = []
@@ -65,11 +74,18 @@ class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
65
74
  self.property = property
66
75
  }
67
76
 
68
- /// Adds a listener to the property and automatically tracks its ID for cleanup
69
- func addListener(_ callback: @escaping (PropertyType.ListenerValueType) -> Void) {
70
- guard let property = property else { return }
77
+ /// Adds a listener to the property and returns a removal function for cleanup
78
+ func addListener(_ callback: @escaping (PropertyType.ListenerValueType) -> Void) -> () -> Void {
79
+ guard let property = property else {
80
+ return {}
81
+ }
71
82
  let id = property.addListener(callback)
72
83
  listenerIds.append(id)
84
+ return { [weak self, weak property] in
85
+ guard let property = property else { return }
86
+ property.removeListener(id)
87
+ self?.listenerIds.removeAll { $0 == id }
88
+ }
73
89
  }
74
90
 
75
91
  func removeListeners() throws {
@@ -94,7 +110,7 @@ protocol ValuedPropertyProtocol<ValueType> {
94
110
  var property: PropertyType! { get }
95
111
  var helper: PropertyListenerHelper<PropertyType> { get }
96
112
 
97
- func addListener(onChanged: @escaping (ValueType) -> Void) throws
113
+ func addListener(onChanged: @escaping (ValueType) -> Void) throws -> () -> Void
98
114
  func removeListeners() throws
99
115
  func dispose() throws
100
116
  }
@@ -112,7 +128,7 @@ extension ValuedPropertyProtocol {
112
128
 
113
129
  /// Automatic addListener() ONLY when ListenerValueType == ValueType (no conversion needed)
114
130
  extension ValuedPropertyProtocol where PropertyType.ListenerValueType == ValueType {
115
- func addListener(onChanged: @escaping (ValueType) -> Void) throws {
116
- helper.addListener(onChanged) // Types match, just forward directly!
131
+ func addListener(onChanged: @escaping (ValueType) -> Void) throws -> () -> Void {
132
+ return helper.addListener(onChanged)
117
133
  }
118
134
  }
@@ -6,12 +6,7 @@ class HybridViewModel: HybridViewModelSpec {
6
6
  init(viewModel: RiveDataBindingViewModel) {
7
7
  self.viewModel = viewModel
8
8
  }
9
-
10
- override init() {
11
- self.viewModel = nil
12
- super.init()
13
- }
14
-
9
+
15
10
  var propertyCount: Double { Double(viewModel?.propertyCount ?? 0) }
16
11
 
17
12
  var instanceCount: Double { Double(viewModel?.instanceCount ?? 0) }
@@ -9,15 +9,7 @@ class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec, Valued
9
9
  self.property = property
10
10
  super.init()
11
11
  }
12
-
13
- /// ⚠️ DO NOT REMOVE
14
- /// Nitro requires a parameterless initializer for JS bridging.
15
- /// This is invoked automatically during hybrid module construction.
16
- /// Internally we always use `init(property:)`
17
- override init() {
18
- super.init()
19
- }
20
-
12
+
21
13
  var value: Bool {
22
14
  get {
23
15
  return property.value
@@ -9,15 +9,7 @@ class HybridViewModelColorProperty: HybridViewModelColorPropertySpec, ValuedProp
9
9
  self.property = property
10
10
  super.init()
11
11
  }
12
-
13
- /// ⚠️ DO NOT REMOVE
14
- /// Nitro requires a parameterless initializer for JS bridging.
15
- /// This is invoked automatically during hybrid module construction.
16
- /// Internally we always use `init(property:)`
17
- override init() {
18
- super.init()
19
- }
20
-
12
+
21
13
  var value: Double {
22
14
  get {
23
15
  return property.value.toHexDouble()
@@ -27,9 +19,8 @@ class HybridViewModelColorProperty: HybridViewModelColorPropertySpec, ValuedProp
27
19
  }
28
20
  }
29
21
 
30
- // Custom addListener because we need to convert UIColor Double
31
- func addListener(onChanged: @escaping (Double) -> Void) throws {
32
- helper.addListener { (color: UIColor) in
22
+ func addListener(onChanged: @escaping (Double) -> Void) throws -> () -> Void {
23
+ return helper.addListener { (color: UIColor) in
33
24
  onChanged(color.toHexDouble())
34
25
  }
35
26
  }
@@ -9,15 +9,7 @@ class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec, ValuedProper
9
9
  self.property = property
10
10
  super.init()
11
11
  }
12
-
13
- /// ⚠️ DO NOT REMOVE
14
- /// Nitro requires a parameterless initializer for JS bridging.
15
- /// This is invoked automatically during hybrid module construction.
16
- /// Internally we always use `init(property:)`
17
- override init() {
18
- super.init()
19
- }
20
-
12
+
21
13
  var value: String {
22
14
  get {
23
15
  return property.value
@@ -1,13 +1,13 @@
1
1
  import RiveRuntime
2
2
 
3
3
  class HybridViewModelImageProperty: HybridViewModelImagePropertySpec, ValuedPropertyProtocol {
4
- func addListener(onChanged: @escaping () -> Void) throws {
5
- try addListener(onChanged: { _ in onChanged() })
6
- }
7
-
8
4
  var property: ImagePropertyType!
9
5
  lazy var helper = PropertyListenerHelper(property: property!)
10
6
 
7
+ func addListener(onChanged: @escaping () -> Void) throws -> () -> Void {
8
+ return helper.addListener { _ in onChanged() }
9
+ }
10
+
11
11
  init(property: ImagePropertyType) {
12
12
  self.property = property
13
13
  super.init()
@@ -6,12 +6,7 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec {
6
6
  init(viewModelInstance: RiveDataBindingViewModel.Instance) {
7
7
  self.viewModelInstance = viewModelInstance
8
8
  }
9
-
10
- override init() {
11
- self.viewModelInstance = nil
12
- super.init()
13
- }
14
-
9
+
15
10
  var instanceName: String { viewModelInstance?.name ?? "" }
16
11
 
17
12
  func numberProperty(path: String) throws -> (any HybridViewModelNumberPropertySpec)? {
@@ -48,4 +43,9 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec {
48
43
  guard let property = viewModelInstance?.imageProperty(fromPath: path) else { return nil }
49
44
  return HybridViewModelImageProperty(property: property)
50
45
  }
46
+
47
+ func listProperty(path: String) throws -> (any HybridViewModelListPropertySpec)? {
48
+ guard let property = viewModelInstance?.listProperty(fromPath: path) else { return nil }
49
+ return HybridViewModelListProperty(property: property)
50
+ }
51
51
  }