Skip to content

Commit 590ede7

Browse files
committed
feat: add RiveFile.fromSource method
1 parent 1d32b5f commit 590ede7

File tree

5 files changed

+96
-10
lines changed

5 files changed

+96
-10
lines changed
60.8 KB
Binary file not shown.

example/assets/rive/rating.riv

15.8 KB
Binary file not shown.

example/metro.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ const { getConfig } = require('react-native-builder-bob/metro-config');
44

55
const root = path.resolve(__dirname, '..');
66

7+
const config = getDefaultConfig(__dirname);
8+
config.resolver.assetExts = [...config.resolver.assetExts, 'riv'];
79
/**
810
* Metro configuration
911
* https://facebook.github.io/metro/docs/configuration
1012
*
1113
* @type {import('metro-config').MetroConfig}
1214
*/
13-
module.exports = getConfig(getDefaultConfig(__dirname), {
15+
module.exports = getConfig(config, {
1416
root,
1517
project: __dirname,
1618
});

example/src/App.tsx

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from 'react-native-rive';
1717
import { useRef, useState, useEffect } from 'react';
1818

19-
type LoadingMethod = 'URL' | 'Resource' | 'ArrayBuffer';
19+
type LoadingMethod = 'URL' | 'Resource' | 'ArrayBuffer' | 'Source';
2020

2121
interface CustomRiveViewProps {
2222
loadingMethod: LoadingMethod;
@@ -60,26 +60,31 @@ const CustomRiveView = ({ loadingMethod, title }: CustomRiveViewProps) => {
6060

6161
const loadRiveFile = async () => {
6262
try {
63-
let riveFile: RiveFile | null = null;
63+
let file: RiveFile | null = null;
6464

6565
switch (loadingMethod) {
6666
case 'URL':
67-
riveFile = await RiveFileFactory.fromURL(networkGraphicURL);
67+
file = await RiveFileFactory.fromURL(networkGraphicURL);
6868
break;
6969
case 'Resource':
70-
riveFile = await RiveFileFactory.fromResource('rewards');
70+
file = await RiveFileFactory.fromResource('rewards');
7171
break;
7272
case 'ArrayBuffer':
7373
const arrayBuffer =
7474
await downloadFileAsArrayBuffer(networkGraphicURL);
7575
if (arrayBuffer) {
76-
riveFile = await RiveFileFactory.fromBytes(arrayBuffer);
76+
file = await RiveFileFactory.fromBytes(arrayBuffer);
7777
}
7878
break;
79+
case 'Source':
80+
file = await RiveFileFactory.fromSource(
81+
require('../assets/rive/rating.riv')
82+
);
83+
break;
7984
}
8085

81-
if (riveFile) {
82-
setRiveFile(riveFile);
86+
if (file) {
87+
setRiveFile(file);
8388
}
8489
} catch (error) {
8590
console.error(`Error loading Rive file from ${loadingMethod}:`, error);
@@ -99,7 +104,7 @@ const CustomRiveView = ({ loadingMethod, title }: CustomRiveViewProps) => {
99104
style={styles.rive}
100105
autoBind={false}
101106
autoPlay={true}
102-
fit={Fit.Cover}
107+
fit={Fit.Contain}
103108
file={riveFile}
104109
hybridRef={{
105110
f: (ref) => {
@@ -115,13 +120,14 @@ const CustomRiveView = ({ loadingMethod, title }: CustomRiveViewProps) => {
115120
};
116121

117122
export default function App() {
118-
const [activeTab, setActiveTab] = useState<LoadingMethod>('URL');
123+
const [activeTab, setActiveTab] = useState<LoadingMethod>('Source');
119124

120125
const renderContent = () => {
121126
const titles = {
122127
URL: 'Loading from URL',
123128
Resource: 'Loading from Resource',
124129
ArrayBuffer: 'Loading from ArrayBuffer',
130+
Source: 'Loading from Source',
125131
};
126132

127133
return (
@@ -133,6 +139,19 @@ export default function App() {
133139
<View style={styles.container}>
134140
{renderContent()}
135141
<View style={styles.tabBar}>
142+
<TouchableOpacity
143+
style={[styles.tab, activeTab === 'Source' && styles.activeTab]}
144+
onPress={() => setActiveTab('Source')}
145+
>
146+
<Text
147+
style={[
148+
styles.tabText,
149+
activeTab === 'Source' && styles.activeTabText,
150+
]}
151+
>
152+
Source
153+
</Text>
154+
</TouchableOpacity>
136155
<TouchableOpacity
137156
style={[styles.tab, activeTab === 'URL' && styles.activeTab]}
138157
onPress={() => setActiveTab('URL')}

src/core/RiveFile.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import type {
44
RiveFileFactory as RiveFileFactoryInternal,
55
} from '../specs/RiveFile.nitro';
66

7+
// This import path isn't handled by @types/react-native
8+
// @ts-ignore
9+
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
10+
711
const RiveFileInternal =
812
NitroModules.createHybridObject<RiveFileFactoryInternal>('RiveFileFactory');
913

@@ -50,4 +54,65 @@ export namespace RiveFileFactory {
5054
): Promise<RiveFile> {
5155
return RiveFileInternal.fromBytes(bytes, loadCdn);
5256
}
57+
58+
/**
59+
* Creates a RiveFile instance from a source that can be either a resource ID or a URI object.
60+
* @param source - Either a number representing a resource ID or an object with a uri property
61+
* @param loadCdn - Whether to load from CDN (default: true)
62+
* @returns Promise that resolves to a RiveFile instance
63+
* @throws Error if the source is invalid or cannot be resolved
64+
* @example
65+
* // Using a resource ID
66+
* const riveFile1 = await RiveFileFactory.fromSource(require('./animation.riv'));
67+
*
68+
* // Using a URI object
69+
* const riveFile2 = await RiveFileFactory.fromSource({ uri: 'https://example.com/animation.riv' });
70+
*
71+
* // Using a local file URI
72+
* const riveFile3 = await RiveFileFactory.fromSource({ uri: 'file:///path/to/animation.riv' });
73+
*
74+
* @note To use .riv files with require(), you need to add 'riv' to the asset extensions in your metro.config.js:
75+
* ```js
76+
* const config = getDefaultConfig(__dirname);
77+
* config.resolver.assetExts = [...config.resolver.assetExts, 'riv'];
78+
* ```
79+
*/
80+
export async function fromSource(
81+
source: number | { uri: string },
82+
loadCdn: boolean = true
83+
): Promise<RiveFile> {
84+
const assetID = typeof source === 'number' ? source : null;
85+
const sourceURI = typeof source === 'object' ? source.uri : null;
86+
87+
const assetURI = assetID ? resolveAssetSource(assetID)?.uri : sourceURI;
88+
89+
if (!assetURI) {
90+
throw new Error(
91+
`Invalid source provided, ${source} is not a valid asset ID or URI`
92+
);
93+
}
94+
95+
try {
96+
// handle http address and dev server
97+
if (assetURI.match(/https?:\/\//)) {
98+
return RiveFileFactory.fromURL(assetURI, loadCdn);
99+
}
100+
101+
// handle iOS bundled asset
102+
if (assetURI.match(/file:\/\//)) {
103+
const match = assetURI.match(/file:\/\/(.*\/)+(.*)\.riv/);
104+
if (!match) {
105+
throw new Error(`Invalid iOS asset path format: ${assetURI}`);
106+
}
107+
return RiveFileFactory.fromResource(match[2], loadCdn);
108+
}
109+
110+
// handle Android bundled asset or resource name uri
111+
return RiveFileFactory.fromResource(assetURI, loadCdn);
112+
} catch (error: unknown) {
113+
const errorMessage =
114+
error instanceof Error ? error.message : String(error);
115+
throw new Error(`Failed to load Rive file from source: ${errorMessage}`);
116+
}
117+
}
53118
}

0 commit comments

Comments
 (0)