rive-app / rive-app/rive-flutter
RiveWidgetBuilder throws LateInitializationError when artboardSelector changes during the initial file load
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Dart
- Sterne
- 1.5k
- Forks
- 240
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Beschreibung
Version: rive 0.14.11, Flutter 3.47.0 / Dart 3.13.0. Reproduces on iOS and Android.
What happens
_RiveWidgetBuilderState keeps the loaded file in late File _file (lib/src/widgets/rive_builder.dart:82), and only the withFileLoad: true path assigns it (:130).
didUpdateWidget routes a changed artboardSelector / stateMachineSelector / dataBind to _setup(withFileLoad: false) (:99), which reads _file without assigning it (:139-:140).
initState starts the load asynchronously (:88). So if one of those selectors changes before that load finishes, _setupImpl reads an unwritten late field:
LateError: LateInitializationError: Field '_file@<hash>' has not been initialized.
package:rive/src/widgets/rive_builder.dart:137 _RiveWidgetBuilderState._setupImpl
package:rive/src/widgets/rive_builder.dart:108 _RiveWidgetBuilderState._setup
package:rive/src/widgets/rive_builder.dart:99 _RiveWidgetBuilderState.didUpdateWidget
Hit in production with a themed selector — ArtboardSelector.byName(isDark ? 'dark' : 'light') flipping when the stored theme resolves shortly after launch, while the asset is still loading.
Selector identity is not the cause: ArtboardNamed implements == on name, so ordinary rebuilds do not enter this path. It needs a real selector change racing the initial load.
Repro
class Repro extends StatefulWidget {
const Repro({super.key});
@override
State<Repro> createState() => _ReproState();
}
class _ReproState extends State<Repro> {
static final _loader = FileLoader.fromAsset(
'assets/animations/example.riv',
riveFactory: Factory.rive,
);
bool _dark = false;
@override
void initState() {
super.initState();
// Change the selector while the first load is still in flight.
Future<void>.delayed(const Duration(milliseconds: 50), () {
if (mounted) setState(() => _dark = true);
});
}
@override
Widget build(BuildContext context) => RiveWidgetBuilder(
fileLoader: _loader,
artboardSelector: ArtboardSelector.byName(_dark ? 'dark' : 'light'),
builder: (context, state) => switch (state) {
RiveLoading() => const SizedBox.shrink(),
RiveFailed() => const SizedBox.shrink(),
RiveLoaded() => RiveWidget(controller: state.controller),
},
);
}
Needs a .riv with dark and light artboards. Tune the delay to land inside the load.
Expected: the selector change waits for the in-flight load, or starts one.
Actual: LateInitializationError, and the widget never reaches RiveLoaded.
Suggested fix
Make the field nullable so a missing file implies a load, rather than trusting the flag:
File? _file;
// in _setupImpl
var file = _file;
if (withFileLoad || file == null) {
file = await widget.fileLoader.file();
if (!mounted || !identical(_currentSetup, thisSetup)) {
return;
}
_file = file;
}
// use `file` below in place of `_file`
FileLoader caches and hands back the in-flight future when a load is already running, so the extra call does not re-read the asset.
Narrower alternative: have didUpdateWidget pass withFileLoad: true while the first load is still outstanding.
Workaround
Key the builder on whatever drives the selector, so a change remounts it instead of updating it:
RiveWidgetBuilder(
key: ValueKey(isDark),
fileLoader: sharedLoader,
artboardSelector: ArtboardSelector.byName(isDark ? 'dark' : 'light'),
...
)
Safe with a shared, app-lifetime FileLoader: the builder's dispose leaves the file and loader alone, and the loader's cache means the remount reuses the decoded file.
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Start in lib/src/widgets/rive_builder.dart, reading _RiveWidgetBuilderState.didUpdateWidget, _setup, and _setupImpl around the cited lines; then run the provided Repro with a selector change during the initial load. Done means the in-flight load is handled without LateInitializationError and the widget reaches RiveLoaded.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- dart, flutter
- Bereich
- frontend, mobile-dev
- Issue-Typ
- Bug
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Klar beschrieben
- Anfängerfreundlichkeit
- 78/100