Skip to main content
Rive6 min readNovember 2026

How do I use a Rive state machine in Flutter?

Short answer

With the current rive package (0.14.x), load the file with FileLoader, render it with RiveWidgetBuilder, and pick the state machine with StateMachineSelector.byName on a RiveWidgetController. To change what the state machine does from your Dart code, Rive now recommends data binding: bind a view model instance and set its properties. Older tutorials use StateMachineController and SMI inputs from 0.13 and earlier.

Core Takeaway

  • rive 0.14.x rewrote the Flutter API on top of rive_native
  • FileLoader + RiveWidgetBuilder handle loading and disposal
  • StateMachineSelector.byName picks a state machine
  • Data binding (view model properties) is the recommended way to drive it
  • Tutorials using StateMachineController and SMIBool are for the legacy API

"How to use Rive's state machine in Flutter" is the most voted Rive question on Stack Overflow, and many answers online target the older API. Version 0.14 changed the package a lot, so check which version a tutorial uses.

Load and render

late final fileLoader = FileLoader.fromAsset(
  'assets/toggle.riv',
  riveFactory: Factory.rive,
);

@override
Widget build(BuildContext context) {
  return RiveWidgetBuilder(
    fileLoader: fileLoader,
    builder: (context, state) => switch (state) {
      RiveLoading() => const CircularProgressIndicator(),
      RiveFailed() => Text(state.error.toString()),
      RiveLoaded() => RiveWidget(controller: state.controller, fit: Fit.contain),
    },
  );
}

Call fileLoader.dispose() in dispose(). Rive also suggests calling await RiveNative.init() early in main().

Pick a state machine

final controller = RiveWidgetController(
  file,
  artboardSelector: ArtboardSelector.byName('Main'),
  stateMachineSelector: StateMachineSelector.byName('Toggle'),
);

If you do not pass a selector, the default state machine is used. A common bug is a file whose default plays a plain animation instead of the state machine you built.

Drive it from Dart with data binding

final vmi = controller.dataBind(DataBind.auto());
final progress = vmi.number('Progress')!;
progress.value = 0.75;
progress.addListener((value) => debugPrint('$value'));

Booleans, triggers, strings and colours work the same way through their own property types; see Rive's data binding docs for each. Ask your designer for the exact view model and property names, because lookups are by name.

ExodeUI works on the same idea, with inputs and events that your Flutter code controls, and stores the file as readable JSON. We make ExodeUI.

Frequently asked questions

Where did StateMachineController go?

It belongs to the legacy runtime (0.13 and earlier). 0.14 uses RiveWidgetController with selectors and data binding.

Can I still use state machine inputs?

Rive now recommends data binding for new work. Check the Rive docs for the status of inputs in your version.

Which renderer should I choose?

Factory.rive uses the Rive renderer; Factory.flutter uses Skia or Impeller. Rive explains the trade-offs in its Flutter docs.

How do I listen for events from the animation?

Add listeners to view model properties, or use Rive events as described in the Flutter runtime docs.

Related Guides & Examples

Compare a Flutter-ready alternative

ExodeUI has a Flutter runtime with state machines, spring physics and inputs from your Dart code.

See the Flutter alternative
ExodeUI

EXODEUI DESKTOP

A little space for good ideas.

Choose your platform to get the official ExodeUI installer.

Looking for Intel Mac or other builds? Explore the web studio or check all download options.

Prefer the browser? Try for free in your browser →