-
Notifications
You must be signed in to change notification settings - Fork 229
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Summary
When using the same Rive widget with data binding in multiple routes, artboards display correctly on the first-level route but show overlapping/incorrect artboards on pushed routes.
Environment
- rive: 0.14.1
- Flutter: 3.35.4 (Dart 3.9.2)
- Platform: iOS / Android
Expected Behavior
The WuzuAvatar widget should display the correct single artboard with data-bound properties (outfit, hat, background, etc.) regardless of which route it's rendered on.
Actual Behavior
- On the first-level route (e.g., home screen): Widget displays correctly with the specified artboard and bound properties.
- On pushed routes (e.g., diary detail, weekly report): Widget displays overlapping artboards that are not the ones specified. Multiple artboards appear to be rendered simultaneously.
Key observation: If I remove all data binding (ViewModelInstance properties), the widget renders correctly without overlapping issues.
Steps to Reproduce
- Create a Rive file with multiple artboards (e.g.,
cat,bear,dog) - Set up a ViewModel with artboard-type properties (e.g.,
clothes_back,clothes_front,hat_front) - Create a Flutter widget that uses
RiveWidgetControllerwithdataBind() - Use the widget on a root-level screen → works correctly
- Navigate to a pushed route and use the same widget → overlapping artboards appear
Code
Widget Implementation
class WuzuAvatar extends ConsumerStatefulWidget {
const WuzuAvatar({
super.key,
this.controller,
this.artboard, // Which artboard to use (cat, bear, dog)
this.collarColor,
this.eyeColor,
this.outfit, // Nested artboard variant
this.hat, // Nested artboard variant
this.background, // Nested artboard variant
this.foreground, // Nested artboard variant
// ... more properties
this.stateMachine,
this.size = 48.0,
this.onTap,
});
// ... properties
@override
ConsumerState<WuzuAvatar> createState() => _WuzuAvatarState();
}
class _WuzuAvatarState extends ConsumerState<WuzuAvatar> {
rive.File? _file;
rive.RiveWidgetController? _riveController;
rive.ViewModelInstance? _viewModelInstance;
rive.ViewModelInstanceArtboard? _clothesBackProperty;
rive.ViewModelInstanceArtboard? _clothesFrontProperty;
rive.ViewModelInstanceArtboard? _hatFrontProperty;
// ... more properties
@override
void initState() {
super.initState();
_loadRiveFile();
}
Future<void> _loadRiveFile() async {
final data = await rootBundle.load('assets/rive/wuzu.riv');
final bytes = data.buffer.asUint8List(
data.offsetInBytes,
data.lengthInBytes,
);
final file = await rive.File.decode(
bytes,
riveFactory: rive.Factory.rive,
);
if (!mounted) return;
setState(() {
_file = file;
_riveController = rive.RiveWidgetController(
file,
artboardSelector: rive.ArtboardSelector.byName(effectiveArtboard.value),
stateMachineSelector: widget.stateMachine != null
? rive.StateMachineSelector.byName(widget.stateMachine!.value)
: const rive.StateMachineDefault(),
);
});
_initDataBinding(effectiveArtboard);
}
void _initDataBinding(WuzuArtboard effectiveArtboard) {
if (_riveController == null || _file == null) return;
// Bind by index (each artboard has its own ViewModel)
_viewModelInstance = _riveController!.dataBind(
rive.DataBind.byIndex(effectiveArtboard.index),
);
if (_viewModelInstance == null) return;
// Bind nested artboard properties
_clothesBackProperty = _viewModelInstance!.artboard('clothes_back');
_clothesFrontProperty = _viewModelInstance!.artboard('clothes_front');
_hatFrontProperty = _viewModelInstance!.artboard('hat_front');
// ... more bindings
// Set initial values
_updateOutfit();
_updateHat();
// ... more updates
}
void _updateArtboard(
rive.ViewModelInstanceArtboard? property,
String prefix,
WuzuVariant? variant,
) {
if (property == null || variant == null || _file == null) return;
final artboardName = '${prefix}_${variant.value}';
final bindableArtboard = _file!.artboardToBind(artboardName);
if (bindableArtboard != null) {
property.value = bindableArtboard;
}
}
void _disposeDataBindingProperties() {
_clothesBackProperty?.dispose();
_clothesFrontProperty?.dispose();
_hatFrontProperty?.dispose();
// ... dispose all properties
_viewModelInstance?.dispose();
_riveController?.dispose();
}
@override
void dispose() {
_disposeDataBindingProperties();
_file?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.size * 1.378,
height: widget.size,
child: rive.RiveWidget(
controller: _riveController!,
fit: rive.Fit.contain,
),
);
}
}Usage on First-Level Route (Works)
// stats_page.dart - Root level route
WuzuAvatar(
controller: _avatarController,
stateMachine: stateMachine,
collarColor: profile.getCollarColor(Theme.of(context).brightness),
eyeColor: profile.getEyeColor(),
outfit: profile.equippedOutfitEnum,
hat: profile.equippedHatEnum,
size: _avatarSize,
)Usage on Pushed Route (Shows Overlapping)
// diary_detail_screen.dart - Pushed via Navigator.push
WuzuAvatar(
collarColor: profile?.getCollarColor(brightness),
eyeColor: profile?.getEyeColor(),
outfit: profile?.equippedOutfitEnum,
hat: profile?.equippedHatEnum,
background: WuzuVariant.hide,
foreground: WuzuVariant.hide,
inner: WuzuVariant.hide,
size: 48,
)Rive File Structure
- Main artboards:
cat,bear,dog(index 0, 1, 2) - Nested artboard slots:
clothes_back,clothes_front,hat_front,hat_back,background,foreground,inner,facial,blush - Variant artboards:
clothes_back_default,clothes_back_rain_coat,hat_front_witch, etc. - ViewModel per main artboard with artboard-type properties for each slot
Workaround
Removing all data binding code makes the artboards render correctly (but without dynamic property binding).
Screenshots
- Correct display on first-level route
- Overlapping artboards on pushed route (down right bottom)

Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working