.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;
Not yet 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.