Skip to content
Open
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
6 changes: 6 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import EventsExample from './pages/RiveEventsExample';
import StateMachineInputsExample from './pages/RiveStateMachineInputsExample';
import TextRunExample from './pages/RiveTextRunExample';
import OutOfBandAssets from './pages/OutOfBandAssets';
import RiveSuspenseExample from './pages/RiveSuspenseExample';

const Examples = [
{
Expand Down Expand Up @@ -46,6 +47,11 @@ const Examples = [
screenId: 'OutOfBandAssets',
component: OutOfBandAssets,
},
{
title: 'Rive Suspense Example',
screenId: 'RiveSuspense',
component: RiveSuspenseExample,
},
{ title: 'Template Page', screenId: 'Template', component: TemplatePage },
] as const;

Expand Down
184 changes: 184 additions & 0 deletions example/src/pages/RiveSuspenseExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {
Text,
View,
StyleSheet,
ActivityIndicator,
ScrollView,
} from 'react-native';
import { Fit, RiveView, RiveSuspense } from 'react-native-rive';
import { Suspense, Component, type ReactNode } from 'react';

class ErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean; error?: Error }
> {
constructor(props: { children: ReactNode; fallback: ReactNode }) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}

render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}

const RiveCard = ({ title, input }: { title: string; input: any }) => {
const riveFile = RiveSuspense.useRiveFile(input);

return (
<View style={styles.card}>
<Text style={styles.cardTitle}>{title}</Text>
<RiveView
style={styles.riveView}
autoBind={false}
autoPlay={true}
fit={Fit.Contain}
file={riveFile}
/>
</View>
);
};

const SuspenseContent = () => {
return (
<ScrollView style={styles.scrollView}>
<Text style={styles.description}>
This example demonstrates the Suspense API. All three cards below use
the same Rive file, which is cached and shared efficiently.
</Text>

<RiveCard
title="Card 1"
input={require('../../assets/rive/rating.riv')}
/>
<RiveCard
title="Card 2"
input={require('../../assets/rive/rating.riv')}
/>
<RiveCard
title="Card 3"
input={require('../../assets/rive/rating.riv')}
/>

<Text style={styles.note}>
Note: All three cards share the same cached RiveFile instance, loaded
only once!
</Text>
</ScrollView>
);
};

export default function RiveSuspenseExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>RiveSuspense Example</Text>

<RiveSuspense.Provider>
<ErrorBoundary
fallback={
<View style={styles.errorContainer}>
<Text style={styles.errorText}>
Failed to load Rive animation
</Text>
</View>
}
>
<Suspense
fallback={
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loadingText}>Loading animations...</Text>
</View>
}
>
<SuspenseContent />
</Suspense>
</ErrorBoundary>
</RiveSuspense.Provider>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginTop: 20,
marginBottom: 10,
color: '#333',
},
description: {
fontSize: 14,
textAlign: 'center',
marginHorizontal: 20,
marginBottom: 20,
color: '#666',
lineHeight: 20,
},
scrollView: {
flex: 1,
},
card: {
margin: 20,
padding: 15,
backgroundColor: '#f5f5f5',
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
cardTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 10,
color: '#333',
},
riveView: {
height: 200,
backgroundColor: '#fff',
borderRadius: 8,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: '#007AFF',
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorText: {
color: 'red',
fontSize: 16,
textAlign: 'center',
padding: 20,
},
note: {
fontSize: 12,
fontStyle: 'italic',
textAlign: 'center',
marginHorizontal: 20,
marginBottom: 40,
color: '#007AFF',
},
});
8 changes: 4 additions & 4 deletions src/core/RiveFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import type {
RiveFileFactory as RiveFileFactoryInternal,
} from '../specs/RiveFile.nitro';

// This import path isn't handled by @types/react-native
// @ts-ignore
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import { Image } from 'react-native';
import type { ResolvedReferencedAssets } from '../hooks/useRiveFile';

const RiveFileInternal =
Expand Down Expand Up @@ -119,7 +117,9 @@ export namespace RiveFileFactory {
const assetID = typeof source === 'number' ? source : null;
const sourceURI = typeof source === 'object' ? source.uri : null;

const assetURI = assetID ? resolveAssetSource(assetID)?.uri : sourceURI;
const assetURI = assetID
? Image.resolveAssetSource(assetID)?.uri
: sourceURI;

if (!assetURI) {
throw new Error(
Expand Down
19 changes: 19 additions & 0 deletions src/hooks/useReferencedAssetsUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useRef, useEffect } from 'react';
import type { RiveFile } from '../specs/RiveFile.nitro';
import type { ResolvedReferencedAssets } from '../utils/riveFileLoading';

export function useReferencedAssetsUpdate(
riveFile: RiveFile | null,
referencedAssets: ResolvedReferencedAssets | undefined
) {
const initialReferencedAssets = useRef(referencedAssets);

useEffect(() => {
if (initialReferencedAssets.current !== referencedAssets) {
if (riveFile && referencedAssets) {
riveFile.updateReferencedAssets({ data: referencedAssets });
initialReferencedAssets.current = referencedAssets;
}
}
}, [referencedAssets, riveFile]);
}
Loading