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.
Version: rive 0.14.11, Flutter 3.47.0 / Dart 3.13.0. Reproduces on iOS and Android.
What happens
_RiveWidgetBuilderStatekeeps the loaded file inlate File _file(lib/src/widgets/rive_builder.dart:82), and only thewithFileLoad: truepath assigns it (:130).didUpdateWidgetroutes a changedartboardSelector/stateMachineSelector/dataBindto_setup(withFileLoad: false)(:99), which reads_filewithout assigning it (:139-:140).initStatestarts the load asynchronously (:88). So if one of those selectors changes before that load finishes,_setupImplreads an unwrittenlatefield: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:
ArtboardNamedimplements==onname, so ordinary rebuilds do not enter this path. It needs a real selector change racing the initial load.Repro
Needs a
.rivwithdarkandlightartboards. 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 reachesRiveLoaded.Suggested fix
Make the field nullable so a missing file implies a load, rather than trusting the flag:
FileLoadercaches 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
didUpdateWidgetpasswithFileLoad: truewhile the first load is still outstanding.Workaround
Key the builder on whatever drives the selector, so a change remounts it instead of updating it:
Safe with a shared, app-lifetime
FileLoader: the builder'sdisposeleaves the file and loader alone, and the loader's cache means the remount reuses the decoded file.