@@ -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+ )
0 commit comments