79185448

Date: 2024-11-13 14:58:58
Score: 1.5
Natty:
Report link

Flutter: Fix Rive Animation Controller Type Casting Error

Problem

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.

Solution

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,
                ),
              ),
      ),
    );
  }
}

Key Points

  1. Proper Input Initialization
// 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');
  1. Error Handling
try {
  // Load and initialize Rive file
} catch (e) {
  print('Error loading Rive file: $e');
}
  1. Null Safety
void _lookup() {
  if (trigger != null) {
    trigger!.fire();
  }
}

Usage Guide

  1. Add Rive Dependency
dependencies:
  rive: ^0.12.4  # Use latest version
  1. Add Asset to pubspec.yaml
flutter:
  assets:
    - assets/bird.riv
  1. Create State Machine in Rive
  1. Implement Interactions
GestureDetector(
  onTap: _lookup,      // Single tap to look up
  onDoubleTap: _toggleDance,  // Double tap to toggle dance
  child: Rive(...),
)

Common Issues & Solutions

  1. Input Not Found
// Check if input exists
final input = stateMachineController!.findSMI('input_name');
if (input == null) {
  print('Input not found in state machine');
}
  1. Controller Disposal
@override
void dispose() {
  stateMachineController?.dispose();
  super.dispose();
}
  1. Loading States
child: _birdArtboard == null
    ? const CircularProgressIndicator()
    : Rive(artboard: _birdArtboard!),

Best Practices

  1. Type-Safe Input Management
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'),
    );
  }
}
  1. Resource Management
// 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));
}
  1. Performance Optimization
// Use const constructor
const BirdAnimation({Key? key}) : super(key: key);

// Cache artboard
final artboard = file.mainArtboard.instance();

Debugging Tips

  1. Check State Machine Name
print('Available state machines: ${file.mainArtboard.stateMachineNames}');
  1. Verify Inputs
stateMachineController?.inputs.forEach((input) {
  print('Input: ${input.name}, Type: ${input.runtimeType}');
});
  1. Monitor State Changes
dance?.addListener(() {
  print('Dance state: ${dance!.value}');
});

Remember to:

Would you like me to explain any specific part in more detail?

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Ends in question mark (2):
  • Low reputation (1):
Posted by: Darshan Parmar