0

Problem: I'm working on a dice roller app using Flutter and Rive animation. The dice animation doesn't work on the first button press, but it works fine from the second press onward.

I loaded the Rive file in the initState() method and set up the animation controller using the StateMachineController. However, the issue persists. I suspect the problem might be related to how the Rive file or its state machine is initialized.

Here are screenshots showing the issue:

Before pressing the button:

before pressing the button

After the first press:

after pressing the button

After the second press:

after the second one

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
class Rolldice extends StatefulWidget {
 const Rolldice({super.key});
 @override
 State<Rolldice> createState() => _RolldiceState();
}
class _RolldiceState extends State<Rolldice> {
 Artboard? riveArtboard;
 var dicevalue = 0;
 SMINumber? diceNumber;
 SMITrigger? rollTrigger;
 bool isRolling = false;
 @override
 void initState() {
 _loadRiveFile();
 super.initState();
 }
 void _loadRiveFile() async {
 try {
 await RiveFile.initialize();
 final data = await rootBundle.load("assets/images/shakeable_dice.riv");
 final file = RiveFile.import(data);
 final artboard = file.mainArtboard;
 final controller =
 StateMachineController.fromArtboard(artboard, 'StateIdle');
 if (controller != null) {
 artboard.addController(controller);
 diceNumber = controller.findSMI('Number');
 rollTrigger = controller.findSMI('Trigger');
 setState(() => riveArtboard = artboard);
 }
 } catch (e) {
 print("Error loading Rive file: $e");
 }
 }
 void _triggerRoll() {
 if (isRolling) return;
 isRolling = true;
 setState(() {
 dicevalue = Random().nextInt(6) + 1;
 });
 if (diceNumber != null) {
 diceNumber!.value = dicevalue.toDouble();
 }
 if (rollTrigger != null) {
 rollTrigger!.fire();
 } else {
 print('rollTrigger is not initialized');
 }
 Future.delayed(const Duration(seconds: 1), () {
 isRolling = false;
 });
 }
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(title: Text('roll dice')),
 body: Center(
 child: Column(
 children: [
 Expanded(
 child: riveArtboard == null
 ? const SizedBox(child: Text("Loading..."),)
 : Center(child: Rive(artboard: riveArtboard!)),
 ),
 Text(dicevalue.toString()),
 ElevatedButton(onPressed: _triggerRoll, child: Text("Roll")),
 ],
 )),
 );
 }
}
DarkBee
14.4k9 gold badges86 silver badges135 bronze badges
asked Jan 17, 2025 at 6:03

1 Answer 1

1

Any Dart function marked async returns a Future, so _loadRiveFile() actually returns Future<void>.

You don't await _loadRiveFile() in initState. For this reason, initState finishes before _loadRiveFile() has returned and your state variables riveArtboard,diceNumber, and rollTrigger are null when your widget is build.

As a side comment, in general, calling setState from within initState makes little sense.

I would recommend refactoring your code, so that your state variables which represent a future value are defined as a Future and use a FutureBuilder to build your widget.

This question has a different context but deals with the same issue of using a future value to build a widget.

answered Jan 17, 2025 at 10:37
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for your detailed explanation, It helped me understand the concept much better.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.