rive-flutter icon indicating copy to clipboard operation
rive-flutter copied to clipboard

Rive texts error with font: LateInitializationError: Field '_makeFont' has not been initialized

Open marchacio opened this issue 4 months ago • 8 comments

Hi everyone. I created a file on Rive that uses Texts using the "Inter" style.

In my flutter app I use a manually imported font in the pubspec.yaml file called "ProductSans" which is not present in google fonts, so I didn't import the google_fonts package into my app.

The problem arises when I run animation in my app; During the screen building phase this error is caused: Error: LateInitializationError: Field '_makeFont' has not been initialized.

marchacio avatar Mar 20 '24 11:03 marchacio

This is my debug:

Error: LateInitializationError: Field '_makeFont' has not been initialized.
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 297:3  throw_
packages/rive_common/src/rive_text_wasm.dart 15:20                           get _makeFont
packages/rive_common/src/rive_text_wasm.dart 647:23                          decodeFont
packages/rive_common/rive_text.dart 478:12                                   decode
packages/rive/src/rive_core/assets/font_asset.dart 63:17                     parseBytes
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54           runBody
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 127:5           _async
packages/rive/src/rive_core/assets/font_asset.dart 62:34                     parseBytes
packages/rive/src/rive_core/assets/font_asset.dart 59:18                     decode
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54           runBody
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 127:5           _async
packages/rive/src/rive_core/assets/font_asset.dart 58:22                     decode
packages/rive/src/core/importers/file_asset_importer.dart 32:9               <fn>
dart-sdk/lib/async/zone.dart 1661:54                                         runUnary
dart-sdk/lib/async/future_impl.dart 162:18                                   handleValue
dart-sdk/lib/async/future_impl.dart 838:44                                   handleValueCallback
dart-sdk/lib/async/future_impl.dart 867:13                                   _propagateToListeners
dart-sdk/lib/async/future_impl.dart 643:5                                    [_completeWithValue]
dart-sdk/lib/async/future_impl.dart 713:7                                    callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                             _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                              _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 181:7           <fn>

marchacio avatar Mar 20 '24 14:03 marchacio

Update

the problem arises ONLY when the Rive.assets(...) widget (containing the text) is built immediately after the initstate.

Some examples:

  • if the Rive.asset(...) containing the text is the first image you see when you access the app, the error is thrown.
  • If the Rive.asset(...) containing the text is NOT shown immediately in the first screen but you have to, for example, scroll a ListView before seeing it, then no exception arises.

Bypass

I got around the problem by modifying my UI and inserting the animation with the text later in a ListView, but this is a very limiting problem.

marchacio avatar Mar 20 '24 17:03 marchacio

Same issue here

jezell avatar Mar 21 '24 20:03 jezell

@marchacio

HACK wrapper, add asset blank.riv with transparent BG and text, then wrap your animation in this dumb component:


class _RiveLoader extends StatefulWidget {
  const _RiveLoader({required this.child});
  final Widget child;

  @override
  State createState() => _RiveLoaderState();
}

class _RiveLoaderState extends State<_RiveLoader> {
  static bool initialized = false;

  bool init = initialized;

  @override
  void initState() {
    super.initState();
    tryLoad();
  }

  void tryLoad() async {
    try {
      await rive.FontAsset.parseBytes(Uint8List.fromList([]));
    } catch (e) {
      final message = e.toString();
      print(message);
      if (message.contains("LateInitializationError")) {
        await Future.delayed(const Duration(milliseconds: 100), () {
          tryLoad();
        });
      } else {
        setState(() {
          initialized = true;
          init = true;
        });
      }
    }
    setState(() {
      init = true;
      initialized = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (init) {
      return widget.child;
    }
    return const Center(child: rive.RiveAnimation.asset("rive/blank.riv"));
  }
}

jezell avatar Mar 21 '24 21:03 jezell

@marchacio

HACK wrapper, add asset blank.riv with transparent BG and text, then wrap your animation in this dumb component:

class _RiveLoader extends StatefulWidget {
  const _RiveLoader({required this.child});
  final Widget child;

  @override
  State createState() => _RiveLoaderState();
}

class _RiveLoaderState extends State<_RiveLoader> {
  static bool initialized = false;

  bool init = initialized;

  @override
  void initState() {
    super.initState();
    tryLoad();
  }

  void tryLoad() async {
    try {
      await rive.FontAsset.parseBytes(Uint8List.fromList([]));
    } catch (e) {
      final message = e.toString();
      print(message);
      if (message.contains("LateInitializationError")) {
        await Future.delayed(const Duration(milliseconds: 100), () {
          tryLoad();
        });
      } else {
        setState(() {
          initialized = true;
          init = true;
        });
      }
    }
    setState(() {
      init = true;
      initialized = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (init) {
      return widget.child;
    }
    return const Center(child: rive.RiveAnimation.asset("rive/blank.riv"));
  }
}

@jezell Not working. Can you explain better please?

marchacio avatar Mar 21 '24 23:03 marchacio

@jezell did you add a blank asset with text in there (and also in your pubspec.yaml) to load and put your animation as the child? If you don't have text in the blank asset it won't work properly. The component just forces it to load the dummy asset to warm up the wasm module, then as soon as it stops failing to be able to parse fonts it will render the child. It will throw the error once inside that component, but it should continue on and render the child when it's ready so it doesn't attempt to play the child before fonts are ready to be rendered.

I should probably just put together a pull request to fix the bug. Looks like what's happening is the component kicks off some portions before the WASM module is initialized if the first thing you try to play is an animation with text in it.

jezell avatar Mar 21 '24 23:03 jezell

Hi all, could you try calling await RiveFile.initializeText() before showing any Rive content.

This should only be needed if you're manually parsing the Rive file or using RiveFile.import. The initialization of the text engine is encapsulated in RiveFile.asset, RiveFile.file, and RiveFile.network. Which I recommend using.

If you're still running into issues please share a reproducible example with you .riv file and the code used for us to investigate. Also be sure to use the latest versions of the runtime.

HayesGordon avatar Mar 26 '24 11:03 HayesGordon

@HayesGordon seems to work here, but seems like if using RiveAnimation.network like we were it should also handle the initialization of the text engine before it displays the file. Extra step is confusing.

jezell avatar Mar 29 '24 06:03 jezell