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

Add ability to preload fonts

Open osaxma opened this issue 4 years ago • 9 comments

Issue

When a Google font is used the first time, the text shows in the fallback font initially, and then shows the actual Google Font as shown in the attached recording.

This behavior appears in both cases:

  • Font is available in assets and GoogleFonts.config.allowRuntimeFetching = false.
  • Font is fetched from network/cache and not available in asset.

The recording is based on the second case in a Flutter Web app, and the font is most likely coming from cache. For the first case, the font-switch is quicker but it is still apparent.

https://user-images.githubusercontent.com/46427323/109412438-0da8a980-79b9-11eb-9779-e098d210985d.mov

Request It'd be nice to have an asynchronous function that can be called and waited for to preload the fonts for a given list of fonts or from all the fonts in the assets. This will allow avoiding this undesirable font-switch from showing up. An example API for the suggested request would be something like this:

final fonts = <String>['Caveat', 'Roboto', ....... , 'Lato'];
await GoogleFonts.preloadFonts(fonts);

// or
await GoogleFonts.preloadAssetFonts();

Additional Context

The following was previously suggested by @clocksmith in #103 but the issue is closed:

When fonts are coming from assets, they still need to be loaded by the FontLoader, which is very fast when loading from assets, but not necessarily done before the first screen. We might need to make a preload function available to support this case (something that calls loadFontIfNecessary).

osaxma avatar Feb 28 '21 09:02 osaxma

  • Would be very useful. Came here to create exactly the same issue.

marchellodev avatar Mar 08 '21 21:03 marchellodev

I came to report the same. +1 👍

dJani97 avatar Apr 25 '21 18:04 dJani97

For this solution, would be it ok to block on the splash screen while the fonts are loaded, causing a slightly delayed start up time?

clocksmith avatar Apr 26 '21 15:04 clocksmith

@clocksmith I think that would be the best.

dJani97 avatar Apr 26 '21 15:04 dJani97

No. This way if the app supports multiple languages, the splash screen will be dramatically delayed, even though the user does not necessarily needs all those fonts for multiple languages.

It would be much better if we had an async function using which we could preload specific fonts the way it is described in this issue.

marchellodev avatar Apr 26 '21 15:04 marchellodev

I also found this issue when drawing some text to the canvas. And since the font was not loaded when the canvas was drawn, the font remains unchanged. Unless I triggered repainting the canvas, it shows the font properly.

TissueFluid avatar Jun 16 '21 22:06 TissueFluid

I also found this issue when drawing some text to the canvas. And since the font was not loaded when the canvas was drawn, the font remains unchanged. Unless I triggered repainting the canvas, it shows the font properly.

This is the same issue as we are facing with google fonts in Flame too, since we are drawing the text directly on the canvas.

spydon avatar Nov 26 '21 17:11 spydon

Wanted to create a same issue. Its a mandatory feature.

npsulav avatar Apr 03 '22 12:04 npsulav

+1 👍

mrcndn avatar May 19 '22 21:05 mrcndn

I also found this issue when drawing some text to the canvas. And since the font was not loaded when the canvas was drawn, the font remains unchanged. Unless I triggered repainting the canvas, it shows the font properly.

This is the same issue as we are facing with google fonts in Flame too, since we are drawing the text directly on the canvas.

Also having issues using it in Flame because it is not preloaded. But in our usecase we are not loading from assets and we are loading the fonts dynamically. It would be nice to await preloading specific fonts.

Something like

await GoogleFonts.getFontAsync()

duck-dev-go avatar Nov 07 '22 21:11 duck-dev-go

Made a feature request here

@spydon seems to be working with flame components also and it is possible to make multiple calls from multiple components to preload a font at the same time.

duck-dev-go avatar Nov 16 '22 00:11 duck-dev-go

same issue +1 currently I'am preloading assets including fonts like this, which seems to work for me:

Future<void> main() async {
  final binding = WidgetsFlutterBinding.ensureInitialized();

  binding.deferFirstFrame();
  binding.addPostFrameCallback((_) async {
    BuildContext context = binding.renderViewElement as BuildContext;
    if (context != null) {
      await precacheImage(const AssetImage("lib/assets/myImage.gif"), context);
      Text(
        "",
        style: GoogleFonts.font1(),
      );
      Text(
        "",
        style: GoogleFonts.font2(),
      );
    }
    binding.allowFirstFrame();
  });
  runApp(const MyApp());
}

mugi-luffy avatar Nov 18 '22 16:11 mugi-luffy

Here is a workaround, in my case I can use it since I'm caching some TextPainter.layout results which can be easily wiped.

It is strongly discouraged to use this in a production environment, as if the font cannot be loaded, the event will not fire.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final fontsReady = systemFontsStream(fontsToLoad: 2).last;
  GoogleFonts.oswald();
  GoogleFonts.lato();

  await fontsReady;

  runApp(const MyApp());
}

Stream<int> systemFontsStream({int? fontsToLoad}) {
  late StreamController<int> controller;
  var loadedFonts = 0;

  void onSystemFontsLoaded() {
    loadedFonts++;

    print('Fonts loaded: $loadedFonts');
    controller.add(loadedFonts);

    if (loadedFonts == fontsToLoad) {
      controller.close();
    }
  }

  void addListener() {
    PaintingBinding.instance.systemFonts.addListener(onSystemFontsLoaded);
  }

  void removeListener() {
    PaintingBinding.instance.systemFonts.removeListener(onSystemFontsLoaded);
  }

  controller = StreamController<int>(
    onListen: addListener,
    onPause: removeListener,
    onResume: addListener,
    onCancel: removeListener,
  );

  return controller.stream;
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Text(
      'Hello, World!',
      style: GoogleFonts.lato(),
    );
  }
}

maRci002 avatar Dec 02 '22 23:12 maRci002

Update: this will be done this quarter

Googlers, see go/preloading-fonts-flutter

guidezpl avatar Feb 03 '23 09:02 guidezpl

@guidezpl quarter is almost up! haha j/k couldn't resist the saying. hope to see this implemented soon. thanks

jtkeyva avatar Apr 10 '23 08:04 jtkeyva

Hello, Is there any way to do it while it is being implemented?

For example is there a way to know if a font was loaded in the FontLoader? I want to know if a google font is already loaded, just checking if the font family already exists, if not I know I should loaded and I wait a delay until is loaded.

Thank you

MiniSuperDev avatar May 13 '23 17:05 MiniSuperDev

@RicardoMudinyane This is a freely available package I work on when I have time. If you are unsatisfied, I encourage you to contribute. Beyond that, I closed this with #195.

guidezpl avatar Jun 19 '23 11:06 guidezpl