Overlapping artboards when using databinding in pushed routes
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Stale
- Tech stack
- dart, flutter
- Domain
- mobile-dev
Research direction
Start by reproducing the issue with the WuzuAvatar implementation, RiveWidgetController, dataBind(), and Navigator.push using the provided artboard and ViewModel setup. Compare root-level and pushed routes; done means data-bound widgets render only the selected artboard with the expected nested variants on both iOS and Android.
Written by the indexing model from the issue text.
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)
- Dominant language
- Dart
- Stars
- 1.5k
- Forks
- 240
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rive-app/rive-flutter
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
rive-app/rive-flutter#640 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
rive-app/rive-flutter#636 · 1 comment ·
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
rive-app/rive-flutter#634 · 5 reactions ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
rive-app/rive-flutter#617 · 6 comments · 14 reactions ·
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
rive-app/rive-flutter#657 · 2 comments ·
All issues in rive-app/rive-flutter
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bus
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100