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
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 AppApproach 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.