rive-app / rive-app/rive-flutter

RiveWidgetBuilder throws LateInitializationError when artboardSelector changes during the initial file load

Đang mở
#653 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
Dart
Star
1.5k
Fork
240
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

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.

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
dart, flutter
Lĩnh vực
frontend, mobile-dev
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
78/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.