When working with Rive animations in Flutter, you might encounter an error when trying to cast inputs using:
trigger = stateMachineController!.inputs.first as SMITrigger;
This error occurs because the type casting is invalid and unsafe.
Here's a complete working example showing how to properly handle Rive animation inputs:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
class BirdAnimation extends StatefulWidget {
const BirdAnimation({Key? key}) : super(key: key);
@override
State<BirdAnimation> createState() => _BirdAnimationState();
}
class _BirdAnimationState extends State<BirdAnimation> {
Artboard? _birdArtboard;
SMITrigger? trigger;
SMIBool? dance;
StateMachineController? stateMachineController;
@override
void initState() {
super.initState();
_loadRiveFile();
}
Future<void> _loadRiveFile() async {
try {
final data = await rootBundle.load('assets/bird.riv');
final file = RiveFile.import(data);
final artboard = file.mainArtboard;
// Initialize state machine controller
stateMachineController = StateMachineController.fromArtboard(
artboard,
"birb" // Your state machine name
);
if (stateMachineController != null) {
artboard.addController(stateMachineController!);
// Properly find and initialize inputs
trigger = stateMachineController!.findSMI('look up');
dance = stateMachineController!.findSMI('dance');
}
setState(() => _birdArtboard = artboard);
} catch (e) {
print('Error loading Rive file: $e');
}
}
void _lookup() {
if (trigger != null) {
trigger!.fire();
}
}
void _toggleDance() {
if (dance != null) {
dance!.change(!dance!.value);
}
}
@override
void dispose() {
stateMachineController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: 400,
width: 400,
child: _birdArtboard == null
? const CircularProgressIndicator()
: GestureDetector(
onTap: _lookup,
onDoubleTap: _toggleDance,
child: Rive(
artboard: _birdArtboard!,
fit: BoxFit.contain,
),
),
),
);
}
}
// Instead of unsafe casting:
// trigger = stateMachineController!.inputs.first as SMITrigger;
// Use findSMI to safely get inputs:
trigger = stateMachineController!.findSMI('look up');
dance = stateMachineController!.findSMI('dance');
try {
// Load and initialize Rive file
} catch (e) {
print('Error loading Rive file: $e');
}
void _lookup() {
if (trigger != null) {
trigger!.fire();
}
}
dependencies:
rive: ^0.12.4 # Use latest version
flutter:
assets:
- assets/bird.riv
GestureDetector(
onTap: _lookup, // Single tap to look up
onDoubleTap: _toggleDance, // Double tap to toggle dance
child: Rive(...),
)
// Check if input exists
final input = stateMachineController!.findSMI('input_name');
if (input == null) {
print('Input not found in state machine');
}
@override
void dispose() {
stateMachineController?.dispose();
super.dispose();
}
child: _birdArtboard == null
? const CircularProgressIndicator()
: Rive(artboard: _birdArtboard!),
class RiveInputs {
final SMITrigger? lookUp;
final SMIBool? dance;
RiveInputs({this.lookUp, this.dance});
static RiveInputs fromController(StateMachineController controller) {
return RiveInputs(
lookUp: controller.findSMI('look up'),
dance: controller.findSMI('dance'),
);
}
}
// Load file once and reuse
late final Future<RiveFile> _riveFile;
@override
void initState() {
super.initState();
_riveFile = rootBundle.load('assets/bird.riv')
.then((data) => RiveFile.import(data));
}
// Use const constructor
const BirdAnimation({Key? key}) : super(key: key);
// Cache artboard
final artboard = file.mainArtboard.instance();
print('Available state machines: ${file.mainArtboard.stateMachineNames}');
stateMachineController?.inputs.forEach((input) {
print('Input: ${input.name}, Type: ${input.runtimeType}');
});
dance?.addListener(() {
print('Dance state: ${dance!.value}');
});
Remember to:
Would you like me to explain any specific part in more detail?