Skip to content

Commit 6712d5b

Browse files
committed
feat: add data binding lists support
Adds ViewModelListProperty for dynamic list management at runtime. Closes #11
1 parent 1f325e7 commit 6712d5b

32 files changed

Lines changed: 1105 additions & 51 deletions

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.margelo.nitro.rive
33
import androidx.annotation.Keep
44
import app.rive.runtime.kotlin.core.File
55
import com.facebook.proguard.annotations.DoNotStrip
6-
import com.margelo.nitro.NitroModules
76
import java.lang.ref.WeakReference
87
import kotlinx.coroutines.CoroutineScope
98
import kotlinx.coroutines.Dispatchers
@@ -69,13 +68,12 @@ class HybridRiveFile : HybridRiveFileSpec() {
6968
val assetsData = referencedAssets.data ?: return
7069
val cache = referencedAssetCache ?: return
7170
val loader = assetLoader ?: return
72-
val context = NitroModules.applicationContext ?: return
7371

7472
val loadJobs = mutableListOf<kotlinx.coroutines.Deferred<Unit>>()
7573

7674
for ((key, assetData) in assetsData) {
7775
val asset = cache[key] ?: continue
78-
loadJobs.add(loader.updateAsset(assetData, asset, context))
76+
loadJobs.add(loader.updateAsset(assetData, asset))
7977
}
8078

8179
if (loadJobs.isNotEmpty()) {

android/src/main/java/com/margelo/nitro/rive/HybridViewModelImageProperty.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class HybridViewModelImageProperty(private val viewModelImage: ViewModelImagePro
1515
}
1616

1717
override fun addListener(onChanged: () -> Unit) {
18-
listeners.add(onChanged)
18+
listeners.add { onChanged() }
1919
ensureValueListenerJob(viewModelImage.valueFlow.map { })
2020
}
2121
}

android/src/main/java/com/margelo/nitro/rive/HybridViewModelInstance.kt

Lines changed: 26 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,66 +11,45 @@ class HybridViewModelInstance(val viewModelInstance: ViewModelInstance) : Hybrid
1111
override val instanceName: String
1212
get() = viewModelInstance.name
1313

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()
1819
} catch (e: ViewModelException) {
19-
return null
20+
null
2021
}
2122
}
2223

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))
3026
}
3127

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))
3930
}
4031

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))
4834
}
4935

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))
5738
}
5839

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))
6642
}
6743

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))
7554
}
7655
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
override fun instanceAt(index: Double): HybridViewModelInstanceSpec? {
17+
return try {
18+
HybridViewModelInstance(listProperty.elementAt(index.toInt()))
19+
} catch (e: IndexOutOfBoundsException) {
20+
null
21+
}
22+
}
23+
24+
override fun addInstance(instance: HybridViewModelInstanceSpec) {
25+
val hybridInstance = instance as? HybridViewModelInstance ?: return
26+
listProperty.add(hybridInstance.viewModelInstance)
27+
}
28+
29+
override fun insertInstance(instance: HybridViewModelInstanceSpec, index: Double) {
30+
val hybridInstance = instance as? HybridViewModelInstance ?: return
31+
listProperty.add(index.toInt(), hybridInstance.viewModelInstance)
32+
}
33+
34+
override fun removeInstance(instance: HybridViewModelInstanceSpec) {
35+
val hybridInstance = instance as? HybridViewModelInstance ?: return
36+
listProperty.remove(hybridInstance.viewModelInstance)
37+
}
38+
39+
override fun removeInstanceAt(index: Double) {
40+
listProperty.removeAt(index.toInt())
41+
}
42+
43+
override fun swap(index1: Double, index2: Double) {
44+
listProperty.swap(index1.toInt(), index2.toInt())
45+
}
46+
47+
override fun addListener(onChanged: () -> Unit) {
48+
listeners.add { onChanged() }
49+
ensureValueListenerJob(listProperty.valueFlow.map { })
50+
}
51+
}

ios/BaseHybridViewModelProperty.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ typealias EnumPropertyType = RiveDataBindingViewModel.Instance.EnumProperty
2323
typealias ColorPropertyType = RiveDataBindingViewModel.Instance.ColorProperty
2424
typealias TriggerPropertyType = RiveDataBindingViewModel.Instance.TriggerProperty
2525
typealias ImagePropertyType = RiveDataBindingViewModel.Instance.ImageProperty
26+
typealias ListPropertyType = RiveDataBindingViewModel.Instance.ListProperty
2627

2728
// Make all Rive property types conform to the protocol
2829
extension BooleanPropertyType: RivePropertyWithListeners {
@@ -56,6 +57,14 @@ extension ImagePropertyType: RivePropertyWithListeners {
5657
}
5758
}
5859

60+
extension ListPropertyType: RivePropertyWithListeners {
61+
typealias ListenerValueType = Void
62+
63+
func addListener(_ callback: @escaping ListenerType) -> UUID {
64+
addListener { callback(()) }
65+
}
66+
}
67+
5968
/// Helper class for managing ViewModel property listeners
6069
class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
6170
private var listenerIds: [UUID] = []

ios/HybridViewModelInstance.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,9 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec {
4848
guard let property = viewModelInstance?.imageProperty(fromPath: path) else { return nil }
4949
return HybridViewModelImageProperty(property: property)
5050
}
51+
52+
func listProperty(path: String) throws -> (any HybridViewModelListPropertySpec)? {
53+
guard let property = viewModelInstance?.listProperty(fromPath: path) else { return nil }
54+
return HybridViewModelListProperty(property: property)
55+
}
5156
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import RiveRuntime
2+
3+
class HybridViewModelListProperty: HybridViewModelListPropertySpec, ValuedPropertyProtocol {
4+
var property: ListPropertyType!
5+
lazy var helper = PropertyListenerHelper(property: property!)
6+
7+
init(property: ListPropertyType) {
8+
self.property = property
9+
super.init()
10+
}
11+
12+
override init() {
13+
super.init()
14+
}
15+
16+
var length: Double {
17+
Double(property.count)
18+
}
19+
20+
func instanceAt(index: Double) throws -> (any HybridViewModelInstanceSpec)? {
21+
guard let instance = property.instance(at: Int(index)) else { return nil }
22+
return HybridViewModelInstance(viewModelInstance: instance)
23+
}
24+
25+
func addInstance(instance: any HybridViewModelInstanceSpec) throws {
26+
guard let hybridInstance = instance as? HybridViewModelInstance,
27+
let viewModelInstance = hybridInstance.viewModelInstance else { return }
28+
property.addInstance(viewModelInstance)
29+
}
30+
31+
func insertInstance(instance: any HybridViewModelInstanceSpec, index: Double) throws {
32+
guard let hybridInstance = instance as? HybridViewModelInstance,
33+
let viewModelInstance = hybridInstance.viewModelInstance else { return }
34+
_ = property.insertInstance(viewModelInstance, at: Int(index))
35+
}
36+
37+
func removeInstance(instance: any HybridViewModelInstanceSpec) throws {
38+
guard let hybridInstance = instance as? HybridViewModelInstance,
39+
let viewModelInstance = hybridInstance.viewModelInstance else { return }
40+
property.removeInstance(viewModelInstance)
41+
}
42+
43+
func removeInstanceAt(index: Double) throws {
44+
property.removeInstance(at: Int(index))
45+
}
46+
47+
func swap(index1: Double, index2: Double) throws {
48+
property.swapInstance(at: Int(index1), withInstanceAt: Int(index2))
49+
}
50+
51+
func addListener(onChanged: @escaping () -> Void) throws {
52+
try addListener(onChanged: { _ in onChanged() })
53+
}
54+
}

nitrogen/generated/android/c++/JHybridViewModelImagePropertySpec.cpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.cpp

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.hpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)