.riv file should load quickly and managing the RiveFile yourself is not necessary. But if you intend to use the same .riv file in multiple parts of your application, or even on the same screen, it might be advantageous to load the file once and keep it in memory.
Example Usage
- Flutter
- React
- React Native
- Web
- Apple
- Android
In Flutter, you are responsible for managing the lifecycle of a Rive file. You can create a
File object directly, or use the FileLoader convenience class with RiveWidgetBuilder. In both cases, you must call dispose() on the object when it’s no longer needed to free up memory.Copy
Ask AI
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class CachedPage extends StatefulWidget {
const CachedPage({super.key});
@override
State<CachedPage> createState() => _CachedPageState();
}
class _CachedPageState extends State<CachedPage> {
var _isRivLoaded = false;
// This is where our cached file will go.
late File _riveFile;
@override
void initState() {
super.initState();
// Once initialized, build the layout by updating _isRivLoaded.
_initRive().whenComplete(
() => setState(() {
_isRivLoaded = true;
}),
);
}
Future<void> _initRive() async {
// Retrieve the Rive file from assets.
_riveFile = (await File.asset(
"assets/rewards_demo.riv",
riveFactory: Factory.rive,
))!;
}
@override
void dispose() {
_riveFile.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isRivLoaded) {
// Both widgets can use the same Rive file because we cached it as state.
final widget1 = RiveWidget(controller: RiveWidgetController(_riveFile));
final widget2 = RiveWidget(controller: RiveWidgetController(_riveFile));
return Scaffold(
body: Row(
children: [
Expanded(child: widget1),
Expanded(child: widget2),
],
),
);
} else {
return CircularProgressIndicator();
}
}
}
To optimize memory usage, reuse the same
File object across multiple RiveWidget instances if they use the same .riv file. This ensures the file is loaded only once and shared in memory.After a
File is disposed, it cannot be used again. To use the same .riv file, create a new File object.Managing State
How you keep the RiveFile alive and share it with widgets depends on your state management approach. For global access, load the file in main or during app startup, and expose it using a package like Provider. If the file is only needed in a specific part of your app, consider loading the file only when required.Memory
Managing the file yourself gives you fine-grained control over memory usage, especially when the same Rive file is used in multiple places or simultaneously in several widgets. Use Flutter DevTools memory tooling to monitor and optimize memory if needed.Network Assets
To load a Rive file from the Internet, useFile.url('YOUR:URL'). For network assets, cache the file in memory to avoid repeated downloads and unnecessary decoding of the file.Here’s a simplified example showing how to integrate the
useRiveFile hook to reuse a RiveFile across componentsCopy
Ask AI
import React, { useState } from 'react';
import { useRiveFile } from '@rive-app/react-canvas';
// Custom Wrapper component to display the Rive animation
const RiveAnimation = ({ riveFile }) => {
const { RiveComponent } = useRive({
riveFile: riveFile,
autoplay: true
});
return <RiveComponent/>;
};
function App() {
const { riveFile, status } = useRiveFile({
src: 'https://cdn.rive.app/animations/myrivefile.riv',
});
const [instanceCount] = useState(5); // Number of RiveAnimation components to render
if (status === 'idle') {
return <div>Idle...</div>;
}
if (status === 'loading') {
return <div>Loading...</div>;
}
if (status === 'failed') {
return <div>Failed to load Rive file.</div>;
}
// Each RiveAnimation component uses the RiveFile we loaded earlier, so it is only fetched and initialized once
return (
<div className="App">
<header className="App-header">Rive Instances</header>
<div className="rive-list">
{Array.from({ length: instanceCount }, (_, index) => (
<RiveAnimation key={`rive-instance-${index}`} riveFile={riveFile} />
))}
</div>
</div>
);
}
export default App;
- New Runtime (Recommended)
- Legacy Runtime
In the new React Native runtime, you always need to load and manage the lifetime of a For network assets, caching the file in memory avoids repeated downloads and unnecessary decoding. The
RiveFile object that is passed to RiveView. The useRiveFile hook handles loading, and you can reuse the same RiveFile across multiple RiveView components to cache it in memory.Here’s an example showing how to cache a Rive file and reuse it across multiple components:Reuse RiveFile example
Copy
Ask AI
import { useState } from 'react';
import { View, ActivityIndicator, Text } from 'react-native';
import {
RiveView,
useRiveFile,
Fit,
type RiveFile,
} from '@rive-app/react-native';
// Custom component to display a Rive animation
const RiveExample = ({ riveFile }: { riveFile: RiveFile }) => {
return (
<RiveView
file={riveFile}
fit={Fit.Contain}
autoPlay={true}
style={{ width: 200, height: 200 }}
/>
);
};
export default function CacheExample() {
// Load the Rive file once using useRiveFile
const { riveFile, isLoading, error } = useRiveFile(
require('../../assets/rive/rating.riv')
);
const [instanceCount] = useState(5); // Number of RiveExample components to render
if (isLoading) {
return <ActivityIndicator size="large" />;
}
if (error || !riveFile) {
return <Text>Failed to load Rive file: {error || 'Unknown error'}</Text>;
}
// Each RiveExample component uses the same RiveFile we loaded earlier,
// so it is only fetched and initialized once
return (
<View style={{ flex: 1, flexDirection: 'row', flexWrap: 'wrap' }}>
{Array.from({ length: instanceCount }, (_, index) => (
<RiveExample key={`rive-instance-${index}`} riveFile={riveFile} />
))}
</View>
);
}
To optimize memory usage, reuse the same
RiveFile object across multiple RiveView instances if they use the same .riv file. This ensures the file is loaded only once and shared in memory.Managing State
How you keep theRiveFile alive and share it with components depends on your state management approach:- Global access: Load the file at the app level or in a context provider, and expose it using React Context or a state management library like Redux or Zustand.
- Component-level: If the file is only needed in a specific part of your app, load it in a parent component and pass it down as props.
- Custom hook: Create a custom hook that manages the
RiveFilelifecycle and provides it to consuming components.
Memory Management
TheuseRiveFile hook automatically manages the lifecycle of the RiveFile object. When the component unmounts or the input changes, the hook will dispose of the previous file and load a new one if needed. This gives you automatic memory management without manual cleanup.Network Assets
To load a Rive file from a remote URL, pass the URL string touseRiveFile:Copy
Ask AI
const { riveFile, isLoading, error } = useRiveFile(
'https://cdn.rive.app/animations/vehicles.riv'
);
useRiveFile hook handles this automatically as long as you reuse the same riveFile object.See Loading Rive Files for more information on different loading methods.Not supported
The following is a basic example to illustrate how to preload a Rive file, and pass the data to multiple Rive instances.
Copy
Ask AI
const rive = require("@rive-app/canvas");
let riveInstances = [];
function loadRiveFile(src, onSuccess, onError) {
const file = new rive.RiveFile({
src: src,
onLoad: () => onSuccess(file),
onLoadError: onError,
});
// Remember to call init() to trigger the load;
file.init().catch(onError);
}
function setupRiveInstance(loadedRiveFile, canvasId) {
const canvas = document.getElementById(canvasId);
if (!canvas) return;
const riveInstance = new rive.Rive({
riveFile: loadedRiveFile,
// Be sure to specify the correct state machine (or animation) name
stateMachines: "Motion", // Name of the State Machine to play
canvas: canvas,
layout: new rive.Layout({
fit: rive.Fit.FitWidth,
alignment: rive.Alignment.Center,
}),
autoplay: true,
onLoad: () => {
// Prevent a blurry canvas by using the device pixel ratio
riveInstance.resizeDrawingSurfaceToCanvas();
},
});
riveInstances.push(riveInstance);
}
// Loads the .riv file and initializes multiple Rive instances using the same loaded RiveFile in memory
loadRiveFile(
"clean_the_car.riv",
(file) => {
setupRiveInstance(file, "rive-canvas-1");
setupRiveInstance(file, "rive-canvas-2");
// You could also store a reference to the loaded RiveFile here so you're able to initialize other Rive instances later.
},
(error) => {
console.error("Failed to load Rive file:", error);
}
);
// Resize the drawing surface for all instances if the window resizes
window.addEventListener(
"resize",
() => {
riveInstances.forEach((instance) => {
if (instance) {
instance.resizeDrawingSurfaceToCanvas();
}
});
},
false
);
Copy
Ask AI
// Cache a RiveFile somewhere to cache for reuse
let file = try! RiveFile(resource: "file", loadCdn: false)
// For example purposes, a type that reuses a single RiveFile
// when creating new view models for given state machines or artboards.
class ViewModelGenerator {
/// The RiveFile to reuse when generating new view models.
private let file: RiveFile
init(file: RiveFile) {
self.file = file
}
// Returns a new view model using a cached RiveFile.
// This means that the RiveFile will not have to be reparsed
// each time a view model is generated.
func viewModel(stateMachine: String?, artboard: String?) -> RiveViewModel {
// While one RiveFile can be cached and reused, each view model
// should have its own model as to not share state.
let model = RiveModel(riveFile: file)
return RiveViewModel(model, stateMachineName: stateMachine, artboardName: artboard)
}
}
RiveViewModel(fileName:) initializer, the Apple runtime does not cache file usage; that has to be handled manually. You may find that when reusing the same file, your memory usage increases (over time) as you create more view models. This is when you should consider caching the underlying file for reuse.Reusing a single RiveFile (when applicable) will reduce the overall memory usage of your application. If your .riv can be reused across multiple views, where each view requires the same file but uses different artboards or state machines, consider caching the RiveFile before creating your view models. While one RiveFile can be cached, to ensure that each view is in its own state, you must create a unique RiveModel per RiveViewModel instance.To cache a rive file in Android, you can use the Rive Please bear in mind that this is only one way to load the bytes, and your implementation may vary based on your app’s architecture. The key point is to create a Rive
File class to load and cache the file. That RiveFile can then be reused across multiple RiveAnimationView instances. Here’s a basic example:Copy
Ask AI
import app.rive.runtime.kotlin.RiveAnimationView
import app.rive.runtime.kotlin.RiveInitializer
import app.rive.runtime.kotlin.core.File
class MainActivity : ComponentActivity() {
var riveFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// Initialize Rive.
AppInitializer.getInstance(applicationContext)
.initializeComponent(RiveInitializer::class.java)
// Load Rive file from assets and cache.
application.assets.open("rewards_demo.riv").use { inputStream ->
val fileBytes = inputStream.readBytes()
riveFile = File(fileBytes)
}
setContent {
Row {
// First cached file usage.
AndroidView(
modifier = Modifier.weight(1f),
factory = { context ->
RiveAnimationView(context).also {
it.setRiveFile(
file = riveFile!!,
stateMachineName = "State Machine 1",
autoBind = true,
)
}
}
)
// Second cached file usage.
AndroidView(
modifier = Modifier.weight(1f),
factory = { context ->
RiveAnimationView(context).also {
it.setRiveFile(
file = riveFile!!,
stateMachineName = "State Machine 1",
autoBind = true,
)
}
}
)
}
}
}
override fun onDestroy() {
riveFile?.release()
super.onDestroy()
}
}
File from the byte array and then set it on the RiveAnimationView.A Rive
File is reference counted and when created has a reference count of 1. Assigning it to a RiveAnimationView will keep an additional reference, but there is still the original reference from its creation. You are responsible for releasing that reference when you are done with the file by calling File::release. If you do not release the file, the native memory will remain until the app is closed, even if the Kotlin object is garbage collected.