Skip to content
Closed
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
5 changes: 3 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
"@react-native-picker/picker": "^2.11.4",
"@react-navigation/native": "^7.1.9",
"@react-navigation/stack": "^7.3.2",
"@tanstack/react-query": "^5.100.10",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-gesture-handler": "2.29.1",
"react-native-nitro-modules": "0.35.0",
"react-native-reanimated": "4.1.5",
"react-native-reanimated": "4.2.0",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.18.0",
"react-native-worklets": "0.6.1"
"react-native-worklets": "0.7.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
59 changes: 32 additions & 27 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PagesList, type PageItem } from './PagesList';
import { HomeMenu } from './shared/HomeMenu';

Expand Down Expand Up @@ -95,38 +96,42 @@ function HomeScreen({ navigation }: { navigation: any }) {
);
}

const queryClient = new QueryClient();

export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#323232',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: 'Rive Examples',
headerRight: HeaderMenuButton,
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#323232',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
{PagesList.map(({ id, component, name }) => (
>
<Stack.Screen
key={id}
name={id}
component={component}
options={{ title: name }}
name="Home"
component={HomeScreen}
options={{
title: 'Rive Examples',
headerRight: HeaderMenuButton,
}}
/>
))}
</Stack.Navigator>
</NavigationContainer>
{PagesList.map(({ id, component, name }) => (
<Stack.Screen
key={id}
name={id}
component={component}
options={{ title: name }}
/>
))}
</Stack.Navigator>
</NavigationContainer>
</QueryClientProvider>
);
}

Expand Down
46 changes: 26 additions & 20 deletions example/src/exercisers/RiveToReactNativeExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {
Switch,
} from 'react-native';
import { useEffect, useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import Animated, {
runOnUI,
useSharedValue,
useAnimatedStyle,
type SharedValue,
} from 'react-native-reanimated';
import { NitroModules } from 'react-native-nitro-modules';
import { scheduleOnUI } from 'react-native-worklets';
import {
Fit,
RiveView,
Expand All @@ -29,7 +29,7 @@ declare global {
var __callMicrotasks: () => void;
}

installWorkletDispatcher(runOnUI);
installWorkletDispatcher(scheduleOnUI);

function useRiveNumberListener(
property: ViewModelNumberProperty | undefined,
Expand All @@ -40,18 +40,16 @@ function useRiveNumberListener(
if (!property) return;

if (useUIThread) {
const boxedProperty = NitroModules.box(property);
const sv = sharedValue;

runOnUI(() => {
scheduleOnUI(() => {
'worklet';
const prop = boxedProperty.unbox();
prop.addListener((value: number) => {
property.addListener((value: number) => {
'worklet';
sv.value = value;
global.__callMicrotasks();
});
})();
});

return () => {
property.removeListeners();
Expand All @@ -78,32 +76,40 @@ export default function RiveToReactNativeExample() {
) : riveFile ? (
<WithViewModelSetup file={riveFile} />
) : (
<Text style={styles.errorText}>{error?.message || 'Unexpected error'}</Text>
<Text style={styles.errorText}>
{error?.message || 'Unexpected error'}
</Text>
)}
</View>
);
}

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

if (!instance || !viewModel) {
const { data: instance, error } = useQuery({
queryKey: ['bouncing-ball-instance', file],
queryFn: async () => {
const vm = await file.defaultArtboardViewModelAsync();
if (!vm) throw new Error('No view model found.');
const inst = await vm.createDefaultInstanceAsync();
if (!inst) throw new Error('Failed to create view model instance');
return inst;
},
});

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

if (!instance) {
return <ActivityIndicator size="large" color="#0000ff" />;
}

return (
<BouncingBallTracker
instance={instance}
Expand Down
24 changes: 12 additions & 12 deletions src/core/WorkletBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ let isInstalled = false;
*
* Call this once at app startup. It will schedule the installation on the UI thread.
*
* @param runOnUI - The runOnUI function from react-native-reanimated
* Requires react-native-worklets >= 0.7.1 for automatic HybridObject serialization.
*
* @param scheduleOnUI - The scheduleOnUI function from react-native-worklets
*
* @example
* ```tsx
* import { installWorkletDispatcher } from '@rive-app/react-native';
* import { runOnUI } from 'react-native-reanimated';
* import { scheduleOnUI } from 'react-native-worklets';
*
* // Call once at app startup
* installWorkletDispatcher(runOnUI);
* installWorkletDispatcher(scheduleOnUI);
* ```
*/
export function installWorkletDispatcher(
runOnUI: <Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
) => (...args: Args) => void
scheduleOnUI: <Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
) => void
): void {
if (isInstalled) {
return;
Expand All @@ -34,11 +37,8 @@ export function installWorkletDispatcher(
const bridge =
NitroModules.createHybridObject<RiveWorkletBridge>('RiveWorkletBridge');

const boxedBridge = NitroModules.box(bridge);

runOnUI(() => {
scheduleOnUI(() => {
'worklet';
const b = boxedBridge.unbox();
b.install();
})();
bridge.install();
});
}
37 changes: 35 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5563,6 +5563,24 @@ __metadata:
languageName: node
linkType: hard

"@tanstack/query-core@npm:5.100.10":
version: 5.100.10
resolution: "@tanstack/query-core@npm:5.100.10"
checksum: d19351b858bd758ad8a860e9333c9b78c1ec4a564b867f592046a60cf8421580d8aefcb3dfc3c67d59c5f8cf3dee9a3074ac07204e37ec54012fd912246d0dbd
languageName: node
linkType: hard

"@tanstack/react-query@npm:^5.100.10":
version: 5.100.10
resolution: "@tanstack/react-query@npm:5.100.10"
dependencies:
"@tanstack/query-core": 5.100.10
peerDependencies:
react: ^18 || ^19
checksum: 90ceb6cafa573a081bcaf03e727ab99e68c71464dc65cede626c3c10c848d136f33ffb6523a04c477448d46d320b202b5d93fbb6f1cb4bef27fd3af3ff43cbee
languageName: node
linkType: hard

"@testing-library/react-hooks@npm:^8.0.1":
version: 8.0.1
resolution: "@testing-library/react-hooks@npm:8.0.1"
Expand Down Expand Up @@ -16321,6 +16339,20 @@ __metadata:
languageName: node
linkType: hard

"react-native-reanimated@npm:4.2.0":
version: 4.2.0
resolution: "react-native-reanimated@npm:4.2.0"
dependencies:
react-native-is-edge-to-edge: 1.2.1
semver: 7.7.3
peerDependencies:
react: "*"
react-native: "*"
react-native-worklets: ">=0.7.0"
checksum: a8a4c321513cdca93a66b90c284c86eb56b05ac7d8989cd36b3e090055b59b9613870891868432835a13d1ddcaad3b1763bb9c7ed267616d90e138625d15ef23
languageName: node
linkType: hard

"react-native-reanimated@npm:4.2.1":
version: 4.2.1
resolution: "react-native-reanimated@npm:4.2.1"
Expand Down Expand Up @@ -16354,6 +16386,7 @@ __metadata:
"@react-native/typescript-config": 0.79.2
"@react-navigation/native": ^7.1.9
"@react-navigation/stack": ^7.3.2
"@tanstack/react-query": ^5.100.10
"@types/deep-equal": ^1.0.4
"@types/react": ^19.0.0
babel-plugin-react-compiler: ^1.0.0
Expand All @@ -16364,10 +16397,10 @@ __metadata:
react-native-gesture-handler: 2.29.1
react-native-harness: 1.0.0
react-native-nitro-modules: 0.35.0
react-native-reanimated: 4.1.5
react-native-reanimated: 4.2.0
react-native-safe-area-context: ^5.4.0
react-native-screens: ~4.18.0
react-native-worklets: 0.6.1
react-native-worklets: 0.7.4
languageName: unknown
linkType: soft

Expand Down
Loading