Skip to content

Commit 771b5ac

Browse files
authored
feat: add properties enumeration to ViewModel and ViewModelInstance (#248)
Adds \`getPropertiesAsync()\` to \`ViewModel\` and \`ViewModelInstance\` for the experimental backend (iOS + Android). ```ts const vm = file.viewModelByName('Person'); const props = await vm.getPropertiesAsync(); // => [{ name: 'age', type: 'number' }, { name: 'name', type: 'string' }, ...] ``` New types: \`ViewModelPropertyType\` and \`ViewModelPropertyInfo\`. Both platforms use the async rive SDK APIs (\`File.getProperties(of:)\` / \`RiveFile.getViewModelProperties(name)\`). Mirrors the legacy backend PR #98.
1 parent 9623f35 commit 771b5ac

39 files changed

Lines changed: 1100 additions & 70 deletions

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class HybridViewModel(private val viewModel: ViewModel) : HybridViewModelSpec()
5353
}
5454
}
5555

56+
override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
57+
return Promise.rejected(UnsupportedOperationException("getPropertiesAsync is not supported on the legacy backend"))
58+
}
59+
5660
override fun getPropertyCountAsync(): Promise<Double> {
5761
return Promise.async { propertyCount }
5862
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class HybridViewModelInstance(val viewModelInstance: ViewModelInstance) : Hybrid
1212
override val instanceName: String
1313
get() = viewModelInstance.name
1414

15+
override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
16+
return Promise.rejected(UnsupportedOperationException("getPropertiesAsync is not supported on the legacy backend"))
17+
}
18+
1519
// Returns null if ViewModelException is thrown for iOS parity
1620
// (iOS SDK returns nil when property not found, Android SDK throws)
1721
private inline fun <T> getPropertyOrNull(block: () -> T): T? {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import app.rive.RiveFile
55
import app.rive.ViewModelInstance
66
import app.rive.ViewModelSource
77
import app.rive.core.CommandQueue
8+
import app.rive.runtime.kotlin.core.ViewModel
89
import com.facebook.proguard.annotations.DoNotStrip
910
import com.margelo.nitro.core.Promise
1011
import kotlinx.coroutines.runBlocking
@@ -22,6 +23,17 @@ class HybridViewModel(
2223
private const val TAG = "HybridViewModel"
2324
}
2425

26+
override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
27+
val name = viewModelName ?: return Promise.resolved(emptyArray())
28+
return Promise.async {
29+
riveFile
30+
.getViewModelProperties(name)
31+
.map { prop ->
32+
ViewModelPropertyInfo(name = prop.name, type = mapPropertyType(prop.type))
33+
}.toTypedArray()
34+
}
35+
}
36+
2537
override val propertyCount: Double
2638
get() {
2739
DeprecationWarning.warn("propertyCount", "getPropertyCountAsync")
@@ -147,3 +159,19 @@ class HybridViewModel(
147159
}
148160
}
149161
}
162+
163+
internal fun mapPropertyType(type: ViewModel.PropertyDataType): ViewModelPropertyType = when (type) {
164+
ViewModel.PropertyDataType.NONE -> ViewModelPropertyType.NONE
165+
ViewModel.PropertyDataType.STRING -> ViewModelPropertyType.STRING
166+
ViewModel.PropertyDataType.NUMBER -> ViewModelPropertyType.NUMBER
167+
ViewModel.PropertyDataType.BOOLEAN -> ViewModelPropertyType.BOOLEAN
168+
ViewModel.PropertyDataType.COLOR -> ViewModelPropertyType.COLOR
169+
ViewModel.PropertyDataType.LIST -> ViewModelPropertyType.LIST
170+
ViewModel.PropertyDataType.ENUM -> ViewModelPropertyType.ENUM
171+
ViewModel.PropertyDataType.TRIGGER -> ViewModelPropertyType.TRIGGER
172+
ViewModel.PropertyDataType.VIEW_MODEL -> ViewModelPropertyType.VIEWMODEL
173+
ViewModel.PropertyDataType.INTEGER -> ViewModelPropertyType.INTEGER
174+
ViewModel.PropertyDataType.SYMBOL_LIST_INDEX -> ViewModelPropertyType.SYMBOLLISTINDEX
175+
ViewModel.PropertyDataType.ASSET_IMAGE -> ViewModelPropertyType.ASSETIMAGE
176+
ViewModel.PropertyDataType.ARTBOARD -> ViewModelPropertyType.ARTBOARD
177+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ class HybridViewModelInstance(
4444
override val instanceName: String
4545
get() = _instanceName ?: ""
4646

47+
override fun getPropertiesAsync(): Promise<Array<ViewModelPropertyInfo>> {
48+
val name = viewModelName ?: return Promise.resolved(emptyArray())
49+
val file = parentFile.riveFile ?: return Promise.resolved(emptyArray())
50+
return Promise.async {
51+
file
52+
.getViewModelProperties(name)
53+
.map { prop ->
54+
ViewModelPropertyInfo(name = prop.name, type = mapPropertyType(prop.type))
55+
}.toTypedArray()
56+
}
57+
}
58+
4759
override fun numberProperty(path: String): HybridViewModelNumberPropertySpec? {
4860
return try {
4961
runBlocking { viewModelInstance.getNumberFlow(path).first() }

ios/legacy/HybridViewModel.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ class HybridViewModel: HybridViewModelSpec {
1313
var instanceCount: Double { Double(viewModel?.instanceCount ?? 0) }
1414

1515
var modelName: String { viewModel?.name ?? "" }
16-
16+
17+
func getPropertiesAsync() throws -> Promise<[ViewModelPropertyInfo]> {
18+
throw RuntimeError.error(withMessage: "getPropertiesAsync is not supported on the legacy backend")
19+
}
20+
1721
func createInstanceByIndex(index: Double) throws -> (any HybridViewModelInstanceSpec)? {
1822
guard index >= 0 else { return nil }
1923
guard let viewModel = viewModel,

ios/legacy/HybridViewModelInstance.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec {
1010

1111
var instanceName: String { viewModelInstance?.name ?? "" }
1212

13+
func getPropertiesAsync() throws -> Promise<[ViewModelPropertyInfo]> {
14+
throw RuntimeError.error(withMessage: "getPropertiesAsync is not supported on the legacy backend")
15+
}
16+
1317
func numberProperty(path: String) throws -> (any HybridViewModelNumberPropertySpec)? {
1418
guard let property = viewModelInstance?.numberProperty(fromPath: path) else { return nil }
1519
return HybridViewModelNumberProperty(property: property)

ios/new/HybridViewModel.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ class HybridViewModel: HybridViewModelSpec {
3434
}
3535
}
3636

37+
func getPropertiesAsync() throws -> Promise<[ViewModelPropertyInfo]> {
38+
return Promise.async {
39+
let props = try await self.file.getProperties(of: self.vmName)
40+
return props.map { ViewModelPropertyInfo(name: $0.name, type: mapPropertyType($0.type)) }
41+
}
42+
}
43+
3744
func getPropertyCountAsync() throws -> Promise<Double> {
3845
return Promise.async {
3946
Double(try await self.file.getProperties(of: self.vmName).count)
@@ -48,7 +55,7 @@ class HybridViewModel: HybridViewModelSpec {
4855

4956
private func createDefaultInstanceImpl() async throws -> (any HybridViewModelInstanceSpec)? {
5057
let vmi = try await self.file.createViewModelInstance(.viewModelDefault(from: .name(self.vmName)))
51-
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker)
58+
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker, file: self.file, vmName: self.vmName)
5259
}
5360

5461
private func createInstanceByIndexImpl(index: Double) async throws -> (any HybridViewModelInstanceSpec)? {
@@ -57,7 +64,7 @@ class HybridViewModel: HybridViewModelSpec {
5764
guard idx >= 0 && idx < names.count else { return nil }
5865
let name = names[idx]
5966
let vmi = try await self.file.createViewModelInstance(.name(name, from: .name(self.vmName)))
60-
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker, instanceName: name)
67+
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker, instanceName: name, file: self.file, vmName: self.vmName)
6168
}
6269

6370
// Deprecated: Use createInstanceByNameAsync instead
@@ -68,7 +75,7 @@ class HybridViewModel: HybridViewModelSpec {
6875

6976
private func createInstanceByNameImpl(name: String) async throws -> (any HybridViewModelInstanceSpec)? {
7077
let vmi = try await self.file.createViewModelInstance(.name(name, from: .name(self.vmName)))
71-
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker, instanceName: name)
78+
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker, instanceName: name, file: self.file, vmName: self.vmName)
7279
}
7380

7481
// Deprecated: Use createInstanceByNameAsync instead
@@ -93,7 +100,7 @@ class HybridViewModel: HybridViewModelSpec {
93100

94101
private func createInstanceImpl() async throws -> (any HybridViewModelInstanceSpec)? {
95102
let vmi = try await self.file.createViewModelInstance(.blank(from: .name(self.vmName)))
96-
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker)
103+
return HybridViewModelInstance(viewModelInstance: vmi, worker: self.worker, file: self.file, vmName: self.vmName)
97104
}
98105

99106
// Deprecated: Use createBlankInstanceAsync instead
@@ -106,3 +113,24 @@ class HybridViewModel: HybridViewModelSpec {
106113
return Promise.async { try await self.createInstanceImpl() }
107114
}
108115
}
116+
117+
func mapPropertyType(_ type: RiveRuntime.ViewModelProperty.DataType) -> ViewModelPropertyType {
118+
switch type {
119+
case .none: return .none
120+
case .string: return .string
121+
case .number: return .number
122+
case .boolean: return .boolean
123+
case .color: return .color
124+
case .list: return .list
125+
case .enum: return .enum
126+
case .trigger: return .trigger
127+
case .viewModel: return .viewmodel
128+
case .integer: return .integer
129+
case .symbolListIndex: return .symbollistindex
130+
case .assetImage: return .assetimage
131+
case .artboard: return .artboard
132+
case .input: return .input
133+
case .any: return .any
134+
@unknown default: return .none
135+
}
136+
}

ios/new/HybridViewModelInstance.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec {
55
let viewModelInstance: ViewModelInstance
66
let worker: Worker
77
private let _instanceName: String
8+
private let file: File?
9+
private let vmName: String?
810

9-
init(viewModelInstance: ViewModelInstance, worker: Worker, instanceName: String = "") {
11+
init(viewModelInstance: ViewModelInstance, worker: Worker, instanceName: String = "", file: File? = nil, vmName: String? = nil) {
1012
self.viewModelInstance = viewModelInstance
1113
self.worker = worker
1214
self._instanceName = instanceName
15+
self.file = file
16+
self.vmName = vmName
1317
}
1418

15-
// TODO: Workaround — rive-ios experimental SDK doesn't expose ViewModelInstance.name.
16-
// Only works when caller knows the name (createInstanceByName). Falls back to "" otherwise.
1719
var instanceName: String { _instanceName }
1820

21+
func getPropertiesAsync() throws -> Promise<[ViewModelPropertyInfo]> {
22+
guard let file = file, let vmName = vmName else { return Promise.resolved(withResult: []) }
23+
return Promise.async {
24+
let props = try await file.getProperties(of: vmName)
25+
return props.map { ViewModelPropertyInfo(name: $0.name, type: mapPropertyType($0.type)) }
26+
}
27+
}
28+
1929
// Note: Unlike legacy API, experimental API can't sync-validate if property exists
2030
// Non-existent properties return wrapper objects that fail on getValue()
2131
// This is a known limitation documented in EXPERIMENTAL_IOS_API.md

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

Lines changed: 36 additions & 2 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)