Kamran Bekirov Logo
Kamran Bekirov's Blog

Everything I know about Flutter

Back

Fix that Google Fonts glitch in Flutter apps

I love google_fonts package for Flutter. Actually no, I don't. It has a long-standing issue regarding font weights. But nevertheless, I use it.

Using it, I'm freed of the burden of adding fonts to my app's assets. And since I use Flutter Web heavily, adding font files increases the size of the app. When using google_fonts package though, the fonts are downloaded when first used on the app.

Notice something? "Fonts are downloaded when first used on the app.". That brings a problem.

See, let's say upon fresh install of your app, the user lands on LoginPage and there you show Text widgets styled with GoogleFonts:

  Text(
    'Welcome!',
    style: GoogleFonts.inter(
      fontSize: 32,
      fontWeight: FontWeight.w700,
    ),
  ),
  const SizedBox(height: 16),
  Text(
    'We\'re happy to see you. Set up your account in just a few simple steps',
    style: GoogleFonts.inter(
      fontSize: 16,
      fontWeight: FontWeight.w400,
    ),
  ),

You know what ugly thing will happen? See below:

Notice font glitches? When users navigate to LoginPage, the text shows in default font until Google Fonts downloads "Inter" (weights 400 and 700). Once downloaded, it switches to your chosen font. This can also take some more time on slow connections.

This is not a good experience for me.

How to fix this? No need for complex workarounds. The package has a solution for this.

This is called "font swapping" issue in the documentation.

How I use it? Two different approaches.

Approach 1: Flutter Web

When using in Flutter Web, I show a loading overlay on top of the app using MaterialApp.builder until the fonts are loaded. This prevents users from seeing the font swap:

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
 
class App extends StatefulWidget {
  const App({super.key});
 
  @override
  State<App> createState() => _AppState();
}
 
class _AppState extends State<App> {
  late Future<void> _googleFontsPending;
 
  @override
  void initState() {
    super.initState();
 
    // Initialize font loading
    _googleFontsPending = GoogleFonts.pendingFonts([
      GoogleFonts.inter(
        fontWeight: FontWeight.w400,
      ),
      GoogleFonts.inter(
        fontWeight: FontWeight.w700,
      ),
      // Add as many different font and their variants as you want
    ]);
  }
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, child) {
        return FutureBuilder(
          future: _googleFontsPending,
          builder: (context, snapshot) {
            final bool loading =
                snapshot.connectionState != ConnectionState.done;
 
            return Stack(
              children: [
                child!,
                // A loading indicator overlay
                IgnorePointer(
                  child: AnimatedOpacity(
                    opacity: loading ? 1 : 0,
                    duration: kThemeAnimationDuration,
                    child: Container(
                      color: Theme.of(context).scaffoldBackgroundColor,
                      height: MediaQuery.of(context).size.height,
                      width: MediaQuery.of(context).size.width,
                      child: const CircularProgressIndicator(),
                    ),
                  ),
                ),
              ],
            );
          },
        );
      },
      home: const HomePage(),
    );
  }
}

Be careful. Your strategy should be up to you to preload fonts used as soon as the app opened and to do others in the background when app is continuing. For this you can do:

  @override
  void initState() {
    super.initState();
 
    // Initialize font loading immediately for fonts used as soon as app opened
    _googleFontsPending = GoogleFonts.pendingFonts([
      GoogleFonts.inter(
        fontWeight: FontWeight.w400,
      ),
      GoogleFonts.inter(
        fontWeight: FontWeight.w700,
      ),
    ]);
 
    // No need to await these fonts. They will be loaded in the background.
    _preloadOtherFonts();
  }
 
  // Initialize font loading in the background for fonts used when app is continuing
  Future<void> _preloadOtherFonts() async {
    await GoogleFonts.pendingFonts([
      GoogleFonts.inter(
        fontWeight: FontWeight.w600,
        fontStyle: FontStyle.italic,
      ),
      GoogleFonts.bricolageGrotesque(
        fontWeight: FontWeight.w900,
      ),
    ]);
  }
UserOrient - Feature Voting Board logo
Sponsored

UserOrient - Feature Voting Board

Tired of building features nobody uses? Let your users vote on what to build next with UserOrient. Stop wasting development time and build only what users actually want.

Add To Your Flutter App

Approach 2: mobile apps

When using in mobile apps, I await the fonts in a splash/startup screen before showing the main app. You show the splash page to users until the app is ready, so they never see the font glitch. I use a StartUpCubit for this, but you can implement it with Cubit, Bloc, ViewModel, Provider, etc.

class StartUpCubit extends Cubit<StartUpState> {
  StartUpCubit() : super(StartUpInitial());
 
  Future<void> preloadFonts() async {
    emit(StartUpLoading());
 
    await GoogleFonts.pendingFonts([
      GoogleFonts.inter(
        fontWeight: FontWeight.w400,
      ),
      GoogleFonts.inter(
        fontWeight: FontWeight.w700,
      ),
    ]);
 
    emit(StartUpSuccess());
 
    _preloadOtherFonts();
  }
 
  Future<void> _preloadOtherFonts() async {
    await GoogleFonts.pendingFonts([
      GoogleFonts.inter(
        fontWeight: FontWeight.w600,
        fontStyle: FontStyle.italic,
      ),
      GoogleFonts.bricolageGrotesque(
        fontWeight: FontWeight.w900,
      ),
    ]);
  }
}

That's it. No more font swapping glitches, more professional looking app on first impression. Hope you find it useful.


Kamran Bekirov

I'm Kamran Bekirov,

Flutter Developer who built over 80 mobile apps,

Founder of UserOrient - Flutter SDK for collecting user feedback.