Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,18 @@ class HybridViewModelInstance(val viewModelInstance: ViewModelInstance) : Hybrid
override fun listProperty(path: String) = getPropertyOrNull {
HybridViewModelListProperty(viewModelInstance.getListProperty(path))
}

override fun viewModelInstanceProperty(path: String) = getPropertyOrNull {
HybridViewModelInstance(viewModelInstance.getInstanceProperty(path))
}

override fun setViewModelInstanceProperty(path: String, instance: HybridViewModelInstanceSpec): Boolean {
return try {
val nativeInstance = (instance as HybridViewModelInstance).viewModelInstance
viewModelInstance.setInstanceProperty(path, nativeInstance)
true
} catch (e: ViewModelException) {
false
}
}
}
Binary file added example/assets/rive/person_databinding_test.riv
Binary file not shown.
204 changes: 204 additions & 0 deletions example/src/pages/NestedViewModelExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import {
View,
Text,
StyleSheet,
ActivityIndicator,
Button,
} from 'react-native';
import { useEffect, useMemo, useState } from 'react';
import {
Fit,
RiveView,
useRiveFile,
useRiveEnum,
type ViewModelInstance,
type RiveFile,
} from '@rive-app/react-native';
import { type Metadata } from '../helpers/metadata';

export default function NestedViewModelExample() {
const { riveFile, isLoading, error } = useRiveFile(
require('../../assets/rive/person_databinding_test.riv')
);

return (
<View style={styles.container}>
<View style={styles.riveContainer}>
{isLoading ? (
<ActivityIndicator size="large" color="#0000ff" />
) : riveFile ? (
<WithViewModelSetup file={riveFile} />
) : (
<Text style={styles.errorText}>{error || 'Unexpected error'}</Text>
)}
</View>
</View>
);
}

function WithViewModelSetup({ file }: { file: RiveFile }) {
const viewModel = useMemo(() => file.defaultArtboardViewModel(), [file]);
const instance = useMemo(
() => viewModel?.createDefaultInstance(),
[viewModel]
);

if (!instance || !viewModel) {
return (
<Text style={styles.errorText}>
{!viewModel
? 'No view model found'
: 'Failed to create view model instance'}
</Text>
);
}

return <NestedViewModelTest instance={instance} file={file} />;
}

function NestedViewModelTest({
instance,
file,
}: {
instance: ViewModelInstance;
file: RiveFile;
}) {
const [testResults, setTestResults] = useState<string[]>([]);

// Test 1: Path notation (like rive-react does)
const { value: drinkType, setValue: setDrinkType } = useRiveEnum(
'favDrink/type',
instance
);

useEffect(() => {
const results: string[] = [];

// Test 1: viewModelInstanceProperty - get nested instance
const nestedDrink = instance.viewModelInstanceProperty('favDrink');
if (nestedDrink) {
results.push(
`✅ viewModelInstanceProperty('favDrink') returned instance`
);
results.push(` Instance name: ${nestedDrink.instanceName}`);

// Try to get enum property from nested instance
const typeFromNested = nestedDrink.enumProperty('type');
if (typeFromNested) {
results.push(
`✅ nestedDrink.enumProperty('type'): ${typeFromNested.value}`
);
} else {
results.push(`❌ nestedDrink.enumProperty('type') returned undefined`);
}
} else {
results.push(
`❌ viewModelInstanceProperty('favDrink') returned undefined`
);
results.push(
` (This file may use path notation, not ViewModel instance properties)`
);
}

// Test 2: Direct path notation
const directEnum = instance.enumProperty('favDrink/type');
if (directEnum) {
results.push(`✅ enumProperty('favDrink/type'): ${directEnum.value}`);
} else {
results.push(`❌ enumProperty('favDrink/type') returned undefined`);
}

setTestResults(results);
}, [instance]);

// Test 3: useRiveEnum hook result (shown separately as it updates reactively)
const useRiveEnumResult = drinkType
? `✅ useRiveEnum('favDrink/type'): ${drinkType}`
: `❌ useRiveEnum('favDrink/type'): undefined`;

return (
<View style={styles.content}>
<RiveView
style={styles.rive}
autoPlay={true}
dataBind={instance}
fit={Fit.Contain}
file={file}
/>

<View style={styles.info}>
<Text style={styles.title}>Test Results:</Text>
{testResults.map((result, i) => (
<Text key={i} style={styles.result}>
{result}
</Text>
))}
<Text style={styles.result}>{useRiveEnumResult}</Text>

<View style={styles.buttons}>
<Button title="Coffee" onPress={() => setDrinkType('Coffee')} />
<Button title="Tea" onPress={() => setDrinkType('Tea')} />
</View>
</View>
</View>
);
}

NestedViewModelExample.metadata = {
name: 'Nested ViewModel',
description:
'Tests viewModelInstanceProperty() for accessing nested ViewModel instances',
} satisfies Metadata;

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
riveContainer: {
flex: 1,
backgroundColor: '#f5f5f5',
},
content: {
flex: 1,
},
rive: {
flex: 1,
width: '100%',
},
info: {
padding: 16,
backgroundColor: '#fff',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
result: {
fontSize: 12,
fontFamily: 'monospace',
color: '#333',
marginVertical: 2,
},
label: {
fontSize: 14,
color: '#666',
marginTop: 12,
},
value: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
buttons: {
flexDirection: 'row',
gap: 12,
marginTop: 16,
},
errorText: {
color: 'red',
textAlign: 'center',
padding: 20,
},
});
1 change: 1 addition & 0 deletions example/src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export { default as ManyViewModels } from './ManyViewModels';
export { default as ResponsiveLayouts } from './ResponsiveLayouts';
export { default as SharedValueListenerExample } from './SharedValueListenerExample';
export { default as MenuListExample } from './MenuListExample';
export { default as NestedViewModelExample } from './NestedViewModelExample';
13 changes: 13 additions & 0 deletions ios/HybridViewModelInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,17 @@ class HybridViewModelInstance: HybridViewModelInstanceSpec {
guard let property = viewModelInstance?.listProperty(fromPath: path) else { return nil }
return HybridViewModelListProperty(property: property)
}

func viewModelInstanceProperty(path: String) throws -> (any HybridViewModelInstanceSpec)? {
guard let instance = viewModelInstance?.viewModelInstanceProperty(fromPath: path) else { return nil }
return HybridViewModelInstance(viewModelInstance: instance)
}

func setViewModelInstanceProperty(path: String, instance: any HybridViewModelInstanceSpec) throws -> Bool {
guard let hybridInstance = instance as? HybridViewModelInstance,
let nativeInstance = hybridInstance.viewModelInstance else {
return false
}
return viewModelInstance?.setViewModelInstanceProperty(fromPath: path, to: nativeInstance) ?? false
}
}
14 changes: 14 additions & 0 deletions nitrogen/generated/android/c++/JHybridViewModelInstanceSpec.cpp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions nitrogen/generated/ios/c++/HybridViewModelInstanceSpecSwift.hpp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions nitrogen/generated/ios/swift/HybridViewModelInstanceSpec_cxx.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading