I am developing a Flutter application using Rive animations for checkboxes. While the checkboxes visually toggle correctly when tapped, the programmatic state does not reflect these changes. Specifically, the submittedMissions list does not update correctly—it fails to remove IDs when checkboxes are unchecked, leading to inaccurate state representation in the application logic.
Steps to Reproduce
Tap a checkbox to select it; the internal state list submittedMissions updates to include the mission ID.
Tap the checkbox again to deselect it; the submittedMissions list fails to remove the mission ID.
The UI shows the checkbox as deselected, but the state still considers it selected.
Expected Behavior
When a checkbox is deselected, the corresponding mission ID should be removed from the submittedMissions list, ensuring the internal state matches the visual representation.
Screenshots
State not updating when checkbox is unchecked - it says there are still 2 checkboxes selected
Expected behavior using Flutter's native Checkbox.
Code to Reproduce
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
void main() {
runApp(
MaterialApp(
home: MissionListScreen(),
debugShowCheckedModeBanner: false,
),
);
}
class MissionListScreen extends StatefulWidget {
@override
_MissionListScreenState createState() => _MissionListScreenState();
}
class _MissionListScreenState extends State<MissionListScreen> {
List<MissionItem> missions = [
MissionItem(missionId: '1', missionName: 'Mission 1'),
MissionItem(missionId: '2', missionName: 'Mission 2'),
MissionItem(missionId: '3', missionName: 'Mission 3'),
];
List<String> submittedMissions = [];
@override
void initState() {
super.initState();
loadRiveFiles();
}
void loadRiveFiles() async {
for (var mission in missions) {
await _loadRiveFile(mission);
}
setState(() {});
}
Future<void> _loadRiveFile(MissionItem missionItem) async {
try {
final data = await rootBundle.load('assets/checkbox.riv');
final file = RiveFile.import(data);
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'State Machine 1');
if (controller != null) {
artboard.addController(controller);
missionItem.checkButtonInput = controller.findInput<bool>('Tap');
missionItem.checkButtonArtboard = artboard;
// Set the initial state of the Tap input
missionItem.checkButtonInput?.value =
submittedMissions.contains(missionItem.missionId);
}
} catch (e) {
print('Failed to load Rive file: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
//show the total number of missions checked
Text(
'There are ${submittedMissions.length} boxes checked.',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Expanded(
child: ListView.builder(
itemCount: missions.length,
itemBuilder: (context, index) {
MissionItem missionItem = missions[index];
return buildMissionListTile(missionItem, index);
},
),
),
],
),
);
}
Widget buildMissionListTile(MissionItem mission, int index) {
return ListTile(
title: Text(mission.missionName),
trailing: GestureDetector(
onTap: () => toggleMission(mission, index),
child: SizedBox(
height: 32,
width: 32,
child: Rive(artboard: mission.checkButtonArtboard!)),
// this code using Checkbox widget DOES work
// child: Checkbox(
// value: mission.checkButtonInput?.value ?? false,
// onChanged: (value) => toggleMission(mission, index),
// )),
),
);
}
void toggleMission(MissionItem mission, int index) {
bool currentState = mission.checkButtonInput?.value ?? false;
setState(() {
mission.checkButtonInput?.value = !currentState;
MissionItem newMission = mission;
if (currentState) {
submittedMissions.removeWhere((id) => id == mission.missionId);
} else {
if (!submittedMissions.contains(mission.missionId)) {
submittedMissions.add(mission.missionId);
}
}
missions.removeAt(index);
missions.insert(index, newMission);
});
print("Submitted Missions: $submittedMissions"); // Debugging
}
}
class MissionItem {
final String missionId;
final String missionName;
SMIInput<bool>? checkButtonInput;
Artboard? checkButtonArtboard;
MissionItem({
required this.missionId,
required this.missionName,
});
}
Please look into why the Rive animations do not trigger state updates as expected when toggling the state of checkboxes despite the UI changing. Any guidance or workarounds would be greatly appreciated!
Description
I am developing a Flutter application using Rive animations for checkboxes. While the checkboxes visually toggle correctly when tapped, the programmatic state does not reflect these changes. Specifically, the
submittedMissions
list does not update correctly—it fails to remove IDs when checkboxes are unchecked, leading to inaccurate state representation in the application logic.Steps to Reproduce
submittedMissions
updates to include the mission ID.submittedMissions
list fails to remove the mission ID.Expected Behavior
When a checkbox is deselected, the corresponding mission ID should be removed from the
submittedMissions
list, ensuring the internal state matches the visual representation.Screenshots
State not updating when checkbox is unchecked - it says there are still 2 checkboxes selected
Expected behavior using Flutter's native Checkbox.
Code to Reproduce
Environment
Additional Context
Request
Please look into why the Rive animations do not trigger state updates as expected when toggling the state of checkboxes despite the UI changing. Any guidance or workarounds would be greatly appreciated!