Skip to content

Commit 1aab7de

Browse files
committed
feat: android add databinding config, error handling, and rive ready hook
1 parent 7915370 commit 1aab7de

5 files changed

Lines changed: 188 additions & 38 deletions

File tree

android/src/main/java/com/rivereactnative/RNRiveError.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ enum class RNRiveError(private val mValue: String) {
1111
IncorrectArtboardName("IncorrectArtboardName"),
1212
IncorrectStateMachineName("IncorrectStateMachineName"),
1313
IncorrectStateMachineInput("IncorrectStateMachineInput"),
14-
TextRunNotFoundError("TextRunNotFoundError");
14+
TextRunNotFoundError("TextRunNotFoundError"),
15+
DataBindingError("DataBindingError");
1516

1617
var message: String = "Default message"
1718

@@ -57,6 +58,11 @@ enum class RNRiveError(private val mValue: String) {
5758
err.message = ex.message!!
5859
return err
5960
}
61+
is ViewModelException -> {
62+
val err = DataBindingError
63+
err.message = ex.message!!
64+
return err;
65+
}
6066
else -> null
6167
}
6268
}

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

Lines changed: 160 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ReactNativeRiveAnimationView(private val context: ThemedReactContext) :
6666
RiveAnimationView(context) {
6767

6868
fun dispose() {
69-
(lifecycleObserver as ReactNativeRiveViewLifecycleObserver).dispose();
69+
(lifecycleObserver as ReactNativeRiveViewLifecycleObserver).dispose()
7070
}
7171

7272
@SuppressLint("VisibleForTests")
@@ -101,7 +101,8 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
101101
private var eventListener: RiveFileController.RiveEventListener
102102
private var assetStore: RiveReactNativeAssetStore? = null
103103
private val scope = CoroutineScope(Dispatchers.Default)
104-
private val propertyListeners = mutableMapOf<String, Job>()
104+
private var dataBindingConfig: DataBindingConfig? = null
105+
private val propertyListeners = mutableMapOf<String, PropertyListener>()
105106

106107
enum class Events(private val mName: String) {
107108
PLAY("onPlay"), PAUSE("onPause"), STOP("onStop"), LOOP_END("onLoopEnd"), STATE_CHANGED("onStateChanged"), RIVE_EVENT(
@@ -362,7 +363,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
362363

363364
fun setTextRunValue(textRunName: String, textValue: String) {
364365
try {
365-
riveAnimationView?.controller?.activeArtboard?.textRun(textRunName)?.text = textValue;
366+
riveAnimationView?.controller?.activeArtboard?.textRun(textRunName)?.text = textValue
366367
} catch (ex: RiveException) {
367368
handleRiveException(ex)
368369
}
@@ -377,7 +378,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
377378
}
378379

379380
private fun getViewModelInstance(): ViewModelInstance? {
380-
return riveAnimationView?.controller?.activeArtboard?.viewModelInstance;
381+
return riveAnimationView?.controller?.activeArtboard?.viewModelInstance
381382
}
382383

383384
fun setBooleanPropertyValue(path: String, value: Boolean) {
@@ -429,40 +430,110 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
429430
}
430431
}
431432

433+
private fun removePropertyListener(key: String) {
434+
propertyListeners[key]?.job?.cancel()
435+
propertyListeners.remove(key)
436+
}
437+
432438
fun registerPropertyListener(path: String, propertyType: String) {
433-
val key = "$propertyType:$path"
439+
val key = "$propertyType:$path:$id"
440+
// Make sure to remove current listeners, as the same listener may have been registered but
441+
// on a new view model instance.
442+
// We play it safe and always remove the listener and re-add it
443+
removePropertyListener(key)
434444

435445
val propertyTypeEnum = RNPropertyType.mapToRNPropertyType(propertyType)
436446

437-
val property = when (propertyTypeEnum) {
438-
RNPropertyType.String -> getViewModelInstance()?.getStringProperty(path)
439-
RNPropertyType.Boolean -> getViewModelInstance()?.getBooleanProperty(path)
440-
RNPropertyType.Number -> getViewModelInstance()?.getNumberProperty(path)
441-
RNPropertyType.Color -> getViewModelInstance()?.getColorProperty(path)
442-
RNPropertyType.Enum -> getViewModelInstance()?.getEnumProperty(path)
443-
RNPropertyType.Trigger -> getViewModelInstance()?.getTriggerProperty(path)
444-
} ?: return
445-
446-
// This should not be required, as JavaScript does a check to ensure
447-
// event emitters are unique. Adding this as a safety.
448-
propertyListeners[key]?.cancel()
449-
450-
val job = scope.launch {
451-
property.valueFlow.collect { value ->
452-
sendEvent(key, value)
447+
try {
448+
val property = when (propertyTypeEnum) {
449+
RNPropertyType.String -> getViewModelInstance()?.getStringProperty(path)
450+
RNPropertyType.Boolean -> getViewModelInstance()?.getBooleanProperty(path)
451+
RNPropertyType.Number -> getViewModelInstance()?.getNumberProperty(path)
452+
RNPropertyType.Color -> getViewModelInstance()?.getColorProperty(path)
453+
RNPropertyType.Enum -> getViewModelInstance()?.getEnumProperty(path)
454+
RNPropertyType.Trigger -> getViewModelInstance()?.getTriggerProperty(path)
455+
} ?: return
456+
val job = scope.launch {
457+
property.valueFlow.collect { value ->
458+
sendEvent(key, value)
459+
}
453460
}
461+
propertyListeners[key] = PropertyListener(path, propertyType, job)
462+
} catch (ex: RiveException) {
463+
handleRiveException(ex)
464+
} catch (ex: Exception) {
465+
showRNRiveError("Unexpected error during data binding configuration", ex)
454466
}
455-
propertyListeners[key] = job
456467
}
457468

458-
private fun sendEvent(eventName: String, value: Any) {
469+
private val loadedTag: String
470+
get() = "RiveReactNativeLoaded:${this.id}"
471+
472+
private fun sendRiveLoadedEvent() {
473+
sendEvent(loadedTag, null)
474+
}
475+
476+
private fun sendEvent(eventName: String, value: Any?) {
459477
context
460478
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
461479
.emit(eventName, value)
462480
}
463481

482+
private fun configureDataBinding() {
483+
try {
484+
val file = riveAnimationView?.controller?.file ?: return
485+
val artboard = riveAnimationView?.controller?.activeArtboard ?: return
486+
val viewModel = file.defaultViewModelForArtboard(artboard)
487+
488+
fun bindInstance(instance: ViewModelInstance) {
489+
riveAnimationView?.controller?.stateMachines?.first()?.viewModelInstance = instance
490+
riveAnimationView?.controller?.activeArtboard?.viewModelInstance = instance
491+
}
492+
493+
when (val config = dataBindingConfig) {
494+
is DataBindingConfig.AutoBind -> {
495+
// Auto binding is done within the view creation
496+
// The whole view needs to be reloaded
497+
shouldBeReloaded = true
498+
}
499+
500+
is DataBindingConfig.Index -> {
501+
bindInstance(viewModel.createInstanceFromIndex(config.index))
502+
}
503+
504+
is DataBindingConfig.Name -> {
505+
bindInstance(viewModel.createInstanceFromName(config.name))
506+
}
507+
508+
is DataBindingConfig.Empty -> {
509+
bindInstance(viewModel.createBlankInstance())
510+
}
511+
512+
null -> {}
513+
}
514+
515+
// Re-add the listeners, as calling registerPropertyListener from JS may have been done
516+
// at a different time than this configuration, and we need to add the listeners to the
517+
// current bound instance
518+
propertyListeners.toList().forEach { (_, listener) ->
519+
registerPropertyListener(listener.path, listener.propertyType)
520+
}
521+
} catch (ex: RiveException) {
522+
handleRiveException(ex)
523+
} catch (ex: Exception) {
524+
showRNRiveError("Unexpected error during data binding configuration", ex)
525+
}
526+
}
527+
528+
// If the user set autoBind to true
529+
private val shouldAutoBind: Boolean
530+
get() {
531+
val dbConfig = dataBindingConfig
532+
return dbConfig is DataBindingConfig.AutoBind && dbConfig.autoBind
533+
}
534+
464535
private fun clearPropertyListeners() {
465-
propertyListeners.values.forEach { it.cancel() }
536+
propertyListeners.values.forEach { it.job.cancel() }
466537
propertyListeners.clear()
467538
}
468539

@@ -488,7 +559,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
488559

489560
fun setFit(rnFit: RNFit) {
490561
val riveFit = RNFit.mapToRiveFit(rnFit)
491-
if (this.fit == riveFit) return;
562+
if (this.fit == riveFit) return
492563
this.fit = riveFit
493564
riveAnimationView?.fit = riveFit
494565
}
@@ -505,7 +576,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
505576
}
506577

507578
fun setAutoplay(autoplay: Boolean) {
508-
if (this.autoplay == autoplay) return;
579+
if (this.autoplay == autoplay) return
509580
this.autoplay = autoplay
510581
shouldBeReloaded = true
511582
}
@@ -523,7 +594,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
523594
// Handle dev mode (URL instead of asset id)
524595
if (scheme != null) {
525596
handleSourceUrl(source, asset)
526-
return;
597+
return
527598
}
528599

529600
// Handle release mode (asset id)
@@ -586,14 +657,14 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
586657

587658
private fun reloadIfNeeded() {
588659
if (shouldBeReloaded) {
589-
assetStore?.dispose();
660+
assetStore?.dispose()
590661
assetStore = referencedAssets?.let {
591662
RiveReactNativeAssetStore(
592663
it, loadAssetHandler = ::loadAsset
593664
)
594665
}
595666
if (assetStore != null) {
596-
riveAnimationView?.setAssetLoader(assetStore);
667+
riveAnimationView?.setAssetLoader(assetStore)
597668
}
598669

599670
url?.let {
@@ -610,11 +681,13 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
610681
fit = this.fit,
611682
alignment = this.alignment,
612683
autoplay = this.autoplay,
613-
autoBind = true, // always autoBind in react native
684+
autoBind = shouldAutoBind,
614685
stateMachineName = this.stateMachineName,
615686
animationName = this.animationName,
616687
artboardName = this.artboardName
617688
)
689+
configureDataBinding()
690+
sendRiveLoadedEvent()
618691
url = null
619692
} catch (ex: RiveException) {
620693
handleRiveException(ex)
@@ -637,11 +710,13 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
637710
fit = this.fit,
638711
alignment = this.alignment,
639712
autoplay = autoplay,
640-
autoBind = true, // always autoBind in react native
713+
autoBind = shouldAutoBind,
641714
stateMachineName = this.stateMachineName,
642715
animationName = this.animationName,
643716
artboardName = this.artboardName
644717
)
718+
configureDataBinding()
719+
sendRiveLoadedEvent()
645720
} catch (ex: RiveException) {
646721
handleRiveException(ex)
647722
}
@@ -674,7 +749,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
674749
return
675750
}
676751

677-
val previousKeys = previousReferencedAssets.keysList();
752+
val previousKeys = previousReferencedAssets.keysList()
678753
val newKeys = referencedAssets.keysList()
679754

680755
if (previousKeys.toSet() != newKeys.toSet()) {
@@ -695,6 +770,42 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
695770
}
696771
}
697772

773+
fun setDataBinding(dataBinding: ReadableMap?) {
774+
dataBinding?.let {
775+
val type = it.getString("type") ?: return
776+
val value = it.getDynamic("value")
777+
val newConfig = when (type) {
778+
"autobind" -> {
779+
if (value.type == ReadableType.Boolean) {
780+
val booleanValue = value.asBoolean()
781+
DataBindingConfig.AutoBind(booleanValue)
782+
} else null
783+
}
784+
785+
"index" -> {
786+
if (value.type == ReadableType.Number) { // React Native numbers are treated as Double
787+
val numberValue = value.asInt()
788+
DataBindingConfig.Index(numberValue)
789+
} else null
790+
}
791+
792+
"name" -> {
793+
if (value.type == ReadableType.String) {
794+
val stringValue = value.asString()
795+
DataBindingConfig.Name(stringValue)
796+
} else null
797+
}
798+
799+
"empty" -> DataBindingConfig.Empty
800+
else -> null
801+
}
802+
if (newConfig != dataBindingConfig) {
803+
dataBindingConfig = newConfig
804+
configureDataBinding()
805+
}
806+
}
807+
}
808+
698809
fun setStateMachineName(stateMachineName: String) {
699810
if (this.stateMachineName == stateMachineName) return
700811
this.stateMachineName = stateMachineName
@@ -732,7 +843,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
732843
}
733844
} catch (ex: RiveException) {
734845
handleRiveException(ex)
735-
null;
846+
null
736847
}
737848
}
738849

@@ -755,7 +866,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
755866
}
756867
} catch (ex: RiveException) {
757868
handleRiveException(ex)
758-
null;
869+
null
759870
}
760871
}
761872

@@ -786,7 +897,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
786897
}
787898
} catch (ex: RiveException) {
788899
handleRiveException(ex)
789-
null;
900+
null
790901
}
791902
}
792903

@@ -809,7 +920,7 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
809920
}
810921
} catch (ex: RiveException) {
811922
handleRiveException(ex)
812-
null;
923+
null
813924
}
814925
}
815926

@@ -943,7 +1054,6 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
9431054
return 0
9441055
}
9451056

946-
9471057
private fun sendErrorToRN(error: RNRiveError) {
9481058
val reactContext = context as ReactContext
9491059
val data = Arguments.createMap()
@@ -1080,3 +1190,16 @@ class RNRiveFileRequest(
10801190
}
10811191
}
10821192
}
1193+
1194+
sealed class DataBindingConfig {
1195+
data class AutoBind(val autoBind: Boolean) : DataBindingConfig()
1196+
data class Index(val index: Int) : DataBindingConfig()
1197+
data class Name(val name: String) : DataBindingConfig()
1198+
data object Empty : DataBindingConfig()
1199+
}
1200+
1201+
data class PropertyListener(
1202+
val path: String,
1203+
val propertyType: String,
1204+
val job: Job
1205+
)

android/src/main/java/com/rivereactnative/RiveReactNativeViewManager.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ class RiveReactNativeViewManager : SimpleViewManager<RiveReactNativeView>() {
263263
view.setReferencedAssets(source)
264264
}
265265

266+
@ReactProp(name = "dataBinding")
267+
fun setDataBinding(view: RiveReactNativeView, source: ReadableMap?) {
268+
view.setDataBinding(source)
269+
}
270+
266271
@ReactProp(name = "stateMachineName")
267272
fun setStateMachineName(view: RiveReactNativeView, stateMachineName: String) {
268273
view.setStateMachineName(stateMachineName)

0 commit comments

Comments
 (0)