rive-app / rive-app/rive-flutter

RiveWidgetBuilder throws LateInitializationError when artboardSelector changes during the initial file load

Aperta
#653 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Lingua principale
Dart
Stelle
1.5k
Fork
240
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

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.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
dart, flutter
Ambito
frontend, mobile-dev
Tipo di issue
Bug
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Attiva
Chiarezza
Specificata chiaramente
Idoneità per principianti
78/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.