Skip to content

Commit 719ebbe

Browse files
committed
fix(andorid): use ios like implementation
1 parent aae4a7e commit 719ebbe

File tree

12 files changed

+183
-88
lines changed

12 files changed

+183
-88
lines changed

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

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,35 @@ import androidx.annotation.Keep
44
import com.facebook.proguard.annotations.DoNotStrip
55
import com.facebook.react.uimanager.ThemedReactContext
66
import com.margelo.nitro.core.Promise
7+
import com.rive.BindData
78
import com.rive.RiveReactNativeView
89
import com.rive.ViewConfiguration
910
import app.rive.runtime.kotlin.core.Fit as RiveFit
1011
import app.rive.runtime.kotlin.core.Alignment as RiveAlignment
1112
import kotlinx.coroutines.Dispatchers
1213
import kotlinx.coroutines.withContext
1314

15+
fun Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.toBindData(): BindData {
16+
return when (this) {
17+
is Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.First -> {
18+
val instance = (this.asFirstOrNull() as? HybridViewModelInstance)?.viewModelInstance
19+
?: throw Error("Invalid ViewModelInstance")
20+
BindData.Instance(instance)
21+
}
22+
is Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.Second -> {
23+
when (this.asSecondOrNull()) {
24+
DataBindMode.AUTO -> BindData.Auto
25+
DataBindMode.NONE -> BindData.None
26+
else -> BindData.None
27+
}
28+
}
29+
is Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.Third -> {
30+
val name = this.asThirdOrNull()?.byName ?: throw Error("Missing byName value")
31+
BindData.ByName(name)
32+
}
33+
}
34+
}
35+
1436
object DefaultConfiguration {
1537
const val AUTOPLAY = true
1638
val FIT = RiveFit.CONTAIN
@@ -24,6 +46,7 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
2446
//region State
2547
override val view: RiveReactNativeView = RiveReactNativeView(context)
2648
private var needsReload = false
49+
private var firstUpdate = true
2750
private var registeredFile: HybridRiveFile? = null
2851
//endregion
2952

@@ -56,7 +79,7 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
5679
set(value) {
5780
if (field != value) {
5881
field = value
59-
needsReload = true
82+
applyDataBinding()
6083
}
6184
}
6285
//endregion
@@ -111,34 +134,62 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
111134
view.getTextRunValue(name, path)
112135
//endregion
113136

137+
//region Data Binding
138+
private fun applyDataBinding() {
139+
val stateMachines = view.riveAnimationView?.controller?.stateMachines
140+
if (stateMachines.isNullOrEmpty()) return
141+
142+
val bindData = dataBind.toBindData()
143+
val stateMachine = stateMachines.first()
144+
145+
when (bindData) {
146+
is BindData.None -> {
147+
// Unbind by setting to null
148+
stateMachine.viewModelInstance = null
149+
}
150+
is BindData.Auto -> {
151+
// Get the default view model and create default instance
152+
val artboard = view.riveAnimationView?.controller?.activeArtboard
153+
val file = view.riveAnimationView?.controller?.file
154+
if (artboard != null && file != null) {
155+
val viewModel = file.defaultViewModelForArtboard(artboard)
156+
val instance = viewModel.createDefaultInstance()
157+
stateMachine.viewModelInstance = instance
158+
}
159+
}
160+
is BindData.Instance -> {
161+
stateMachine.viewModelInstance = bindData.instance
162+
}
163+
is BindData.ByName -> {
164+
val artboard = view.riveAnimationView?.controller?.activeArtboard
165+
val file = view.riveAnimationView?.controller?.file
166+
if (artboard != null && file != null) {
167+
val viewModel = file.defaultViewModelForArtboard(artboard)
168+
val instance = viewModel.createInstanceFromName(bindData.name)
169+
stateMachine.viewModelInstance = instance
170+
}
171+
}
172+
}
173+
174+
// Only play on subsequent updates, not first
175+
if (!firstUpdate) {
176+
view.riveAnimationView?.controller?.stateMachines?.first()?.name?.let { smName ->
177+
view.riveAnimationView?.play(smName, isStateMachine = true)
178+
}
179+
}
180+
}
181+
//endregion
114182

115183
//region Update
116184
fun refreshAfterAssetChange() {
117185
afterUpdate()
118186
}
119187

120188
override fun afterUpdate() {
189+
firstUpdate = false
121190
val hybridFile = file as? HybridRiveFile
122191
val riveFile = hybridFile?.riveFile ?: return
123192

124-
val (bindMode, bindInstance, bindInstanceName) = when (dataBind) {
125-
is Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.First -> {
126-
val instance = (dataBind.asFirstOrNull() as? HybridViewModelInstance)?.viewModelInstance
127-
Triple(com.rive.BindData.INSTANCE, instance, null)
128-
}
129-
is Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.Second -> {
130-
when (dataBind.asSecondOrNull()) {
131-
DataBindMode.AUTO -> Triple(com.rive.BindData.AUTO, null, null)
132-
DataBindMode.NONE -> Triple(com.rive.BindData.NONE, null, null)
133-
else -> Triple(com.rive.BindData.NONE, null, null)
134-
}
135-
}
136-
is Variant_HybridViewModelInstanceSpec_DataBindMode_DataBindByName.Third -> {
137-
val name = dataBind.asThirdOrNull()?.byName
138-
Triple(com.rive.BindData.BY_NAME, null, name)
139-
}
140-
}
141-
142193
val config = ViewConfiguration(
143194
artboardName = artboardName,
144195
stateMachineName = stateMachineName,
@@ -147,9 +198,7 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
147198
alignment = convertAlignment(alignment) ?: DefaultConfiguration.ALIGNMENT,
148199
fit = convertFit(fit) ?: DefaultConfiguration.FIT,
149200
layoutScaleFactor = layoutScaleFactor?.toFloat() ?: DefaultConfiguration.LAYOUTSCALEFACTOR,
150-
bindMode = bindMode,
151-
bindInstance = bindInstance,
152-
bindInstanceName = bindInstanceName
201+
bindData = dataBind.toBindData()
153202
)
154203
view.configure(config, needsReload)
155204

android/src/main/java/com/rive/RiveReactNativeView.kt

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ import com.margelo.nitro.rive.RiveEventType
2121
import com.margelo.nitro.rive.UnifiedRiveEvent as RNEvent
2222
import kotlinx.coroutines.CompletableDeferred
2323

24-
enum class BindData {
25-
NONE,
26-
AUTO,
27-
INSTANCE,
28-
BY_NAME
24+
sealed class BindData {
25+
data object None : BindData()
26+
data object Auto : BindData()
27+
data class Instance(val instance: ViewModelInstance) : BindData()
28+
data class ByName(val name: String) : BindData()
2929
}
3030

3131
data class ViewConfiguration(
@@ -36,9 +36,7 @@ data class ViewConfiguration(
3636
val alignment: Alignment,
3737
val fit: Fit,
3838
val layoutScaleFactor: Float?,
39-
val bindMode: BindData,
40-
val bindInstance: ViewModelInstance? = null,
41-
val bindInstanceName: String? = null
39+
val bindData: BindData
4240
)
4341

4442
@SuppressLint("ViewConstructor")
@@ -68,7 +66,7 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
6866
artboardName = config.artboardName,
6967
stateMachineName = config.stateMachineName,
7068
autoplay = config.autoPlay,
71-
autoBind = config.bindMode == BindData.AUTO,
69+
autoBind = config.bindData is BindData.Auto,
7270
alignment = config.alignment,
7371
fit = config.fit
7472
)
@@ -90,30 +88,26 @@ class RiveReactNativeView(context: ThemedReactContext) : FrameLayout(context) {
9088
}
9189

9290
val stateMachines = riveAnimationView?.controller?.stateMachines
93-
when (config.bindMode) {
94-
BindData.NONE -> {
91+
when (val bindData = config.bindData) {
92+
is BindData.None -> {
9593
// No binding
9694
}
97-
BindData.AUTO -> {
95+
is BindData.Auto -> {
9896
// Auto-binding handled by setRiveFile above
9997
}
100-
BindData.INSTANCE -> {
101-
config.bindInstance?.let { vmi ->
102-
if (!stateMachines.isNullOrEmpty()) {
103-
stateMachines.first().viewModelInstance = vmi
104-
}
98+
is BindData.Instance -> {
99+
if (!stateMachines.isNullOrEmpty()) {
100+
stateMachines.first().viewModelInstance = bindData.instance
105101
}
106102
}
107-
BindData.BY_NAME -> {
108-
config.bindInstanceName?.let { name ->
109-
val artboard = riveAnimationView?.controller?.activeArtboard
110-
val file = riveAnimationView?.controller?.file
111-
if (artboard != null && file != null) {
112-
val viewModel = file.defaultViewModelForArtboard(artboard)
113-
val instance = viewModel.createInstanceFromName(name) // TODO handle error
114-
if (!stateMachines.isNullOrEmpty()) {
115-
stateMachines.first().viewModelInstance = instance
116-
}
103+
is BindData.ByName -> {
104+
val artboard = riveAnimationView?.controller?.activeArtboard
105+
val file = riveAnimationView?.controller?.file
106+
if (artboard != null && file != null) {
107+
val viewModel = file.defaultViewModelForArtboard(artboard)
108+
val instance = viewModel.createInstanceFromName(bindData.name)
109+
if (!stateMachines.isNullOrEmpty()) {
110+
stateMachines.first().viewModelInstance = instance
117111
}
118112
}
119113
}

example/src/pages/MIklosViewModels.tsx

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
2-
import { useState } from 'react';
2+
import { useState, useMemo } from 'react';
33
import type { Metadata } from '../helpers/metadata';
4-
import { DataBindMode, RiveView, useRiveFile } from 'react-native-rive';
4+
import {
5+
DataBindMode,
6+
RiveView,
7+
useRiveFile,
8+
type ViewModelInstance,
9+
} from 'react-native-rive';
510

6-
type BindModeOption = 'none' | 'auto' | 'red' | 'green' | 'blue';
11+
type BindModeOption =
12+
| 'none'
13+
| 'auto'
14+
| 'red'
15+
| 'green'
16+
| 'blue'
17+
| 'green-instance';
718

8-
function getDataBindValue(mode: BindModeOption) {
19+
function getDataBindValue(
20+
mode: BindModeOption,
21+
greenInstance: ViewModelInstance | null
22+
) {
923
if (mode === 'none') return DataBindMode.None;
1024
if (mode === 'auto') return DataBindMode.Auto;
25+
if (mode === 'green-instance' && greenInstance) return greenInstance;
1126
return { byName: mode };
1227
}
1328

@@ -17,34 +32,54 @@ export default function DataBindingMode() {
1732
);
1833
const [bindMode, setBindMode] = useState<BindModeOption>('none');
1934

20-
const dataBindValue = getDataBindValue(bindMode);
35+
// Create a ViewModelInstance for "green" to demonstrate instance binding
36+
const greenInstance = useMemo(() => {
37+
if (!riveFile) return null;
38+
try {
39+
const viewModel = riveFile.defaultArtboardViewModel();
40+
if (!viewModel) return null;
41+
return viewModel.createInstanceByName('green');
42+
} catch (e) {
43+
console.error('Failed to create green instance:', e);
44+
return null;
45+
}
46+
}, [riveFile]);
47+
48+
const dataBindValue = getDataBindValue(bindMode, greenInstance);
2149

2250
return (
2351
<View style={styles.container}>
2452
<View style={styles.controlsContainer}>
2553
<Text style={styles.label}>Binding Mode:</Text>
2654
<View style={styles.buttonRow}>
27-
{(['none', 'auto', 'red', 'green', 'blue'] as BindModeOption[]).map(
28-
(mode) => (
29-
<TouchableOpacity
30-
key={mode}
55+
{(
56+
[
57+
'none',
58+
'auto',
59+
'red',
60+
'green',
61+
'blue',
62+
'green-instance',
63+
] as BindModeOption[]
64+
).map((mode) => (
65+
<TouchableOpacity
66+
key={mode}
67+
style={[
68+
styles.button,
69+
bindMode === mode && styles.buttonActive,
70+
]}
71+
onPress={() => setBindMode(mode)}
72+
>
73+
<Text
3174
style={[
32-
styles.button,
33-
bindMode === mode && styles.buttonActive,
75+
styles.buttonText,
76+
bindMode === mode && styles.buttonTextActive,
3477
]}
35-
onPress={() => setBindMode(mode)}
3678
>
37-
<Text
38-
style={[
39-
styles.buttonText,
40-
bindMode === mode && styles.buttonTextActive,
41-
]}
42-
>
43-
{mode}
44-
</Text>
45-
</TouchableOpacity>
46-
)
47-
)}
79+
{mode === 'green-instance' ? 'green (instance)' : mode}
80+
</Text>
81+
</TouchableOpacity>
82+
))}
4883
</View>
4984
</View>
5085
{riveFile && (
@@ -62,7 +97,7 @@ export default function DataBindingMode() {
6297
DataBindingMode.metadata = {
6398
name: 'Miklos View Models',
6499
description:
65-
'Interactive data binding mode selector (none, auto, red, green, blue)',
100+
'Interactive data binding mode selector (none, auto, byName, and instance)',
66101
} satisfies Metadata;
67102

68103
const styles = StyleSheet.create({

example/src/pages/OutOfBandAssets.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
/*Rive, */ Fit,
1212
/*RNRiveError,*/ useRiveFile,
1313
RiveView,
14+
DataBindMode,
1415
} from 'react-native-rive';
1516
import { Picker } from '@react-native-picker/picker';
1617
import { type Metadata } from '../helpers/metadata';
@@ -65,6 +66,7 @@ export default function StateMachine() {
6566
fit={Fit.Contain}
6667
style={styles.animation}
6768
stateMachineName="State Machine 1"
69+
dataBind={DataBindMode.None}
6870
// The `referencedAssets` prop allows you to load external assets from various sources:
6971
// - A URI
7072
// - A bundled asset on the native platform (iOS and Android)

example/src/pages/RiveDataBindingExample.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,8 @@ function DataBindingExample({
9595
return (
9696
<RiveView
9797
style={styles.rive}
98-
autoBind={false}
9998
autoPlay={true}
100-
bind={instance}
99+
dataBind={instance}
101100
fit={Fit.Layout}
102101
layoutScaleFactor={1}
103102
file={file}

example/src/pages/RiveEventsExample.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
useRiveFile,
88
type RiveEvent,
99
RiveEventType,
10+
DataBindMode,
1011
} from 'react-native-rive';
1112
import { type Metadata } from '../helpers/metadata';
1213

@@ -46,7 +47,7 @@ export default function EventsExample() {
4647
) : riveFile ? (
4748
<RiveView
4849
style={styles.rive}
49-
autoBind={false}
50+
dataBind={DataBindMode.None}
5051
autoPlay={true}
5152
fit={Fit.Contain}
5253
file={riveFile}

0 commit comments

Comments
 (0)