Integrating Rive Animations into Mobile Applications
Rive differs fundamentally from Lottie: it's not an export of a "baked" animation but a runtime engine with a State Machine. The animation responds to input from code—a button tap changes the State Machine state, smoothly transitioning between animation clips. This is powerful and requires understanding how Rive Runtime works inside your app.
Rive Runtime Architecture
A Rive file (.riv) contains artboards, animation clips, and a State Machine. The runtime loads the file, creates an Artboard instance, and controls it via StateMachineController or SimpleAnimation. Rendering uses Rive's own Renderer, which uses Metal on iOS and OpenGL ES 3.0 / Vulkan on Android.
Important: Rive Renderer and the standard Canvas renderer coexist. On Android, runtime versions before 9.x used Canvas renderer by default—slower but more compatible. From 9.x onwards, Rive Renderer is enabled by default and significantly faster for complex mesh animations, but requires OpenGL ES 3.0 (API 18+, covering >99% of devices).
Android Integration
// build.gradle
implementation "app.rive:rive-android:9.6.0"
// Initialize in Application.onCreate() - mandatory!
RiveInitializer.initializer(context)
// In layout XML:
// <app.rive.runtime.kotlin.RiveAnimationView
// android:id="@+id/riveView"
// app:riveResource="@raw/my_animation"
// app:riveStateMachineName="StateMachine" />
// In Activity/Fragment:
val riveView = findViewById<RiveAnimationView>(R.id.riveView)
// Pass input to State Machine:
riveView.setNumberState("StateMachine", "progress", 0.75f)
riveView.setBooleanState("StateMachine", "isActive", true)
riveView.fireState("StateMachine", "triggerName")
RiveInitializer.initializer() must be called once before creating any RiveAnimationView—otherwise you get IllegalStateException: Rive not initialized. Common mistake with lazy DI framework initialization.
iOS Integration
// SPM: https://github.com/rive-app/rive-ios, version 6.x
import RiveRuntime
let riveView = RiveView()
let model = RiveModel(fileName: "my_animation")
let viewModel = RiveViewModel(model, stateMachineName: "StateMachine")
viewModel.setView(riveView)
// Manage State Machine:
try? viewModel.setInput("isActive", value: true)
try? viewModel.setInput("progress", value: 0.75)
try? viewModel.triggerInput("tapTrigger")
For SwiftUI, use RiveViewModel as an ObservableObject:
struct AnimatedButton: View {
@StateObject private var rvm = RiveViewModel(fileName: "button_animation",
stateMachineName: "ButtonSM")
var body: some View {
rvm.view()
.onTapGesture { try? rvm.triggerInput("pressed") }
}
}
Flutter
// pubspec.yaml: rive: ^0.13.0
import 'package:rive/rive.dart';
RiveAnimation.asset(
'assets/animations/my_animation.riv',
stateMachines: ['StateMachine'],
onInit: (artboard) {
final controller = StateMachineController.fromArtboard(artboard, 'StateMachine');
artboard.addController(controller!);
final isActive = controller.findInput<bool>('isActive') as SMIBool;
isActive.value = true;
},
)
Common Integration Issues
Rive file created for one artboard but runtime tries to access another—NullPointerException with no clear message. Always explicitly specify artboard name via artboardName parameter.
State Machine input names are case and whitespace sensitive. "Is Active" and "isActive" are different inputs. Verify in Rive Editor before integration.
Large .riv files (>5 MB) with embedded raster textures should be loaded asynchronously. On iOS, initialize RiveModel with a Data object loaded in a background thread via URLSession or FileManager.
Timeline
Integrating a single Rive file with basic State Machine takes from 4 hours. With bidirectional synchronization between State Machine and app business logic (progress, auth states, game events), allow 1–2 days. Cost is calculated individually.







