Caching a Rive File
Under most circumstances a .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
// 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) } }
When using the 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.
The following is a basic example to illustrate how to preload a Rive file, and pass the data to multiple Rive instances:
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 );
Not yet supported
The following is a basic example to illustrate how to preload a Rive file, and pass the data directly to the RiveAnimation.direct()
constructor:
class PreloadRive extends StatefulWidget { const PreloadRive({super.key}); @override State<PreloadRive> createState() => _PreloadRiveState(); } class _PreloadRiveState extends State<PreloadRive> { RiveFile? _file; // You can maintain this reference to have a cached version @override void initState() { super.initState(); preload(); } Future<void> preload() async { rootBundle.load('assets/little_machine.riv').then( (data) async { // Load the RiveFile from the binary data. setState(() { _file = RiveFile.import(data); }); }, ); } @override Widget build(BuildContext context) { return (_file == null) ? const SizedBox.shrink() : RiveAnimation.direct(_file!); } }
Other Considerations
Managing State
How the RiveFile
is kept alive in state and shared to other widgets is up to you and your preferred state management solution. One approach can be to wait for the Rive file to load in main
, or during the startup screen, and using the Provider package to expose the data to the whole application.
Or if the animation is only needed in a nested section of your application, then it might be preferable to delay loading the animation until necessary.
Memory
By managing the RiveFile yourself you can have finer control over the memory used in your application. This will be especially beneficial if the same Rive file is being used in multiple parts of your application, or used simultaneously in multiple RiveAnimation
widgets.
You can make use of Flutter DevTools’ memory tooling for additional investigation if needed or desired.
Network Assets
The easiest way to load a Rive animation over the Internet is by using RiveAnimation.network(url)
. However, similar considerations apply to a network asset regarding memory and sharing a Rive file across multiple widgets/pages.
The following can be used to load a Rive file over the network :
final riveFile = await RiveFile.network('YOUR:URL');
The reference can then be kept alive in memory and the riveFile
can be passed to RiveAnimation.direct
.
Here’s a simplified example showing how to integrate the useRiveFile
hook to reuse a RiveFile
across components
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, 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 === '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;