Skip to content

Commit b790796

Browse files
authored
feat: add data binding lists support (#71)
## Summary Adds `ViewModelListProperty` for dynamic list management at runtime, for now no hook just low level methods ### API - `length` - get list size - `instanceAt(index)` - get ViewModelInstance at index - `append(instance)` - add to end of list - `insert(instance, index)` - insert at position - `remove(instance)` - remove by reference - `removeAt(index)` - remove at index - `swap(index1, index2)` - swap items - `addListener(callback)` - observe changes ### Example ```tsx const listProperty = instance.listProperty('items'); // Add new item const newItem = viewModel.createInstance(); listProperty.append(newItem); // Remove item listProperty.removeAt(0); // Swap items listProperty.swap(0, 1); ``` ## Test plan - [x] Added example page with list.riv test file - [x] iOS implementation - [x] Android implementation Closes #11
1 parent 7800820 commit b790796

33 files changed

Lines changed: 1401 additions & 48 deletions

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

Lines changed: 0 additions & 1 deletion
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

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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

example/assets/list.riv

858 KB
Binary file not shown.

0 commit comments

Comments
 (0)