Visitar URL original
Add ability to wait for fonts to load with `GoogleFonts.pendingFonts`… · flutter/packages@7b57377 · GitHub
Skip to content

Commit 7b57377

Browse files
johnsonmhguidezplesouthren
authored
Add ability to wait for fonts to load with GoogleFonts.pendingFonts (#195)
* add proposal for font load function * Remove unneeded imports, update deprecated field names * Whitespace fixes * Fix async tests * Flutter format * improve implementation * update example * Update CHANGELOG.md * flutter pub get (beta) * Create google_fonts_text_style_test.dart * Update CHANGELOG.md * address feedback --------- Co-authored-by: Pierre-Louis <6655696+guidezpl@users.noreply.github.com> Co-authored-by: esouthren <eilidhsouthren@gmail.com> Co-authored-by: Pierre-Louis Guidez <plg@google.com>
1 parent 9cea727 commit 7b57377

7 files changed

Lines changed: 256 additions & 41 deletions

File tree

‎packages/google_fonts/CHANGELOG.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Unreleased
2+
### Added
3+
- Add ability to wait for fonts to load with `GoogleFonts.pendingFonts`
4+
15
## 5.0.0 - 2023-06-01
26
### Added
37
- `Alkatra`
@@ -506,4 +510,4 @@
506510

507511
- ttf files are downloaded via http on demand, and saved to local disk so that they can be loaded without making another http request for future font requests
508512

509-
- Fonts are loaded asynchronously through the font loader and Text widgets that use them are refreshed when they are ready
513+
- Fonts are loaded asynchronously through the font loader and Text widgets that use them are refreshed when they are ready

‎packages/google_fonts/README.md‎

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ A Flutter package to use fonts from [fonts.google.com](https://fonts.google.com/
1414
- Font file caching, on device file system
1515
- Font bundling in assets. Matching font files found in assets are prioritized over HTTP fetching. Useful for offline-first apps.
1616

17-
## Getting Started
17+
## Usage
1818

1919
For example, say you want to use the [Lato](https://fonts.google.com/specimen/Lato) font from Google Fonts in your Flutter app.
2020

21-
First, add the `google_fonts` package to your [pubspec dependencies](https://pub.dev/packages/google_fonts/install).
21+
1. Add the `google_fonts` package to your [pubspec dependencies](https://pub.dev/packages/google_fonts/install).
2222

23-
To import `GoogleFonts`:
23+
1. Import `GoogleFonts`.
24+
```dart
25+
import 'package:google_fonts/google_fonts.dart';
26+
```
2427
25-
```dart
26-
import 'package:google_fonts/google_fonts.dart';
27-
```
28-
29-
To use `GoogleFonts` with the default TextStyle:
28+
### Text styles
29+
To use `GoogleFonts` with the default `TextStyle`:
3030
3131
```dart
3232
Text(
@@ -70,14 +70,16 @@ To override the `fontSize`, `fontWeight`, or `fontStyle`:
7070
Text(
7171
'This is Google Fonts',
7272
style: GoogleFonts.lato(
73-
textStyle: Theme.of(context).textTheme.headline4,
73+
textStyle: Theme.of(context).textTheme.displayLarge,
7474
fontSize: 48,
7575
fontWeight: FontWeight.w700,
7676
fontStyle: FontStyle.italic,
7777
),
7878
),
7979
```
8080

81+
### Text themes
82+
8183
You can also use `GoogleFonts.latoTextTheme()` to make or modify an entire text theme to use the "Lato" font.
8284

8385
```dart
@@ -104,13 +106,18 @@ final textTheme = Theme.of(context).textTheme;
104106
MaterialApp(
105107
theme: ThemeData(
106108
textTheme: GoogleFonts.latoTextTheme(textTheme).copyWith(
107-
bodyText2: GoogleFonts.oswald(textStyle: textTheme.bodyText2),
109+
bodyMedium: GoogleFonts.oswald(textStyle: textTheme.bodyMedium),
108110
),
109111
),
110112
);
111113
```
112114

113-
## HTTP fetching
115+
### Visual font swapping
116+
To avoid visual font swaps that occur when a font is loading, use [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) and [GoogleFonts.pendingFonts()](https://pub.dev/documentation/google_fonts/latest/google_fonts/GoogleFonts/pendingFonts.html).
117+
118+
See the [example app](https://github.com/material-foundation/flutter-packages/blob/main/packages/google_fonts/example/lib/main.dart).
119+
120+
### HTTP fetching
114121

115122
For HTTP fetching to work, certain platforms require additional steps when running the app in debug and/or release mode. For example, macOS requires the following be present in the relevant .entitlements file:
116123

@@ -121,7 +128,7 @@ For HTTP fetching to work, certain platforms require additional steps when runni
121128

122129
Learn more at https://docs.flutter.dev/development/data-and-backend/networking#platform-notes.
123130

124-
## Font bundling in assets
131+
## Bundling fonts when releasing
125132

126133
The `google_fonts` package will automatically use matching font files in your `pubspec.yaml`'s
127134
`assets` (rather than fetching them at runtime via HTTP). Once you've settled on the fonts

‎packages/google_fonts/example/lib/main.dart‎

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ class MyApp extends StatelessWidget {
1010
Widget build(BuildContext context) {
1111
return MaterialApp(
1212
title: 'Flutter Demo',
13-
theme: ThemeData(
14-
primarySwatch: Colors.blue,
15-
),
13+
theme: ThemeData.light(useMaterial3: true),
1614
home: const MyHomePage(title: 'Flutter Demo Home Page'),
1715
);
1816
}
@@ -29,34 +27,56 @@ class MyHomePage extends StatefulWidget {
2927

3028
class MyHomePageState extends State<MyHomePage> {
3129
int _counter = 0;
30+
late Future googleFontsPending;
3231

3332
void _incrementCounter() {
3433
setState(() {
3534
_counter++;
3635
});
3736
}
3837

38+
@override
39+
void initState() {
40+
super.initState();
41+
42+
googleFontsPending = GoogleFonts.pendingFonts([
43+
GoogleFonts.poppins(),
44+
GoogleFonts.montserrat(fontStyle: FontStyle.italic),
45+
]);
46+
}
47+
3948
@override
4049
Widget build(BuildContext context) {
50+
final pushButtonTextStyle = GoogleFonts.poppins(
51+
textStyle: Theme.of(context).textTheme.headlineMedium,
52+
);
53+
final counterTextStyle = GoogleFonts.montserrat(
54+
fontStyle: FontStyle.italic,
55+
textStyle: Theme.of(context).textTheme.displayLarge,
56+
);
57+
4158
return Scaffold(
4259
appBar: AppBar(
4360
title: Text(widget.title),
4461
),
4562
body: Center(
46-
child: Column(
47-
mainAxisAlignment: MainAxisAlignment.center,
48-
children: <Widget>[
49-
Text(
50-
'You have pushed the button this many times:',
51-
style: GoogleFonts.alike(
52-
textStyle: Theme.of(context).textTheme.displayMedium,
53-
),
54-
),
55-
Text(
56-
'$_counter',
57-
style: GoogleFonts.lato(fontStyle: FontStyle.italic),
58-
),
59-
],
63+
child: FutureBuilder(
64+
future: googleFontsPending,
65+
builder: (context, snapshot) {
66+
if (snapshot.connectionState != ConnectionState.done) {
67+
return const SizedBox();
68+
}
69+
return Column(
70+
mainAxisAlignment: MainAxisAlignment.center,
71+
children: <Widget>[
72+
Text(
73+
'You have pushed the button this many times:',
74+
style: pushButtonTextStyle,
75+
),
76+
Text('$_counter', style: counterTextStyle),
77+
],
78+
);
79+
},
6080
),
6181
),
6282
floatingActionButton: FloatingActionButton(

‎packages/google_fonts/generator/google_fonts.tmpl‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,57 @@ class GoogleFonts {
3838
/// ```
3939
static final config = _Config();
4040

41+
/// Returns a [Future] which resolves when requested fonts have finished
42+
/// loading and are ready to be rendered on screen.
43+
///
44+
/// Usage:
45+
/// ```dart
46+
/// GoogleFonts.lato();
47+
/// GoogleFonts.pacificoTextTheme();
48+
/// await GoogleFonts.pendingFonts(); // <-- waits until Lato and Pacifico files have loaded.
49+
/// ```
50+
///
51+
/// To keep things tidy, on can also pass in requested fonts as a list
52+
/// to [pendingFonts].
53+
///
54+
/// ```dart
55+
/// await GoogleFonts.pendingFonts([
56+
/// GoogleFonts.lato(),
57+
/// GoogleFonts.pacificoTextTheme()
58+
/// ]);
59+
/// ```
60+
///
61+
/// To avoid visual font swaps that occur when a font is loading,
62+
/// consider using [FutureBuilder]. Note: This future cannot be created in
63+
/// [build], as described in [FutureBuilder]'s documentation.
64+
///
65+
/// ```dart
66+
/// late Future googleFontsPending;
67+
///
68+
/// @override
69+
/// void initState() {
70+
/// super.initState();
71+
/// googleFontsPending = GoogleFonts.pendingFonts([
72+
/// ...
73+
/// ]);
74+
/// }
75+
///
76+
/// @override
77+
/// Widget build(BuildContext context) {
78+
/// return FutureBuilder(
79+
/// future: googleFontsPending,
80+
/// builder: (context, snapshot) {
81+
/// if (snapshot.connectionState != ConnectionState.done) {
82+
/// return const SizedBox();
83+
/// }
84+
/// ...
85+
/// }
86+
/// );
87+
/// }
88+
/// ```
89+
static Future<List<void>> pendingFonts([List<dynamic>? _]) =>
90+
Future.wait(pendingFontFutures);
91+
4192
/// Get a map of all available fonts.
4293
///
4394
/// Returns a map where the key is the name of the font family and the value
@@ -225,4 +276,4 @@ class GoogleFonts {
225276
}
226277

227278
{{/method}}
228-
}
279+
}

‎packages/google_fonts/lib/google_fonts.dart‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// found in the LICENSE file.
66

77
import 'dart:ui' as ui;
8+
89
import 'package:flutter/material.dart';
10+
911
import 'src/google_fonts_base.dart';
1012
import 'src/google_fonts_descriptor.dart';
1113
import 'src/google_fonts_variant.dart';
@@ -38,6 +40,57 @@ class GoogleFonts {
3840
/// ```
3941
static final config = _Config();
4042

43+
/// Returns a [Future] which resolves when requested fonts have finished
44+
/// loading and are ready to be rendered on screen.
45+
///
46+
/// Usage:
47+
/// ```dart
48+
/// GoogleFonts.lato();
49+
/// GoogleFonts.pacificoTextTheme();
50+
/// await GoogleFonts.pendingFonts(); // <-- waits until Lato and Pacifico files have loaded.
51+
/// ```
52+
///
53+
/// To keep things tidy, on can also pass in requested fonts as a list
54+
/// to [pendingFonts].
55+
///
56+
/// ```dart
57+
/// await GoogleFonts.pendingFonts([
58+
/// GoogleFonts.lato(),
59+
/// GoogleFonts.pacificoTextTheme()
60+
/// ]);
61+
/// ```
62+
///
63+
/// To avoid visual font swaps that occur when a font is loading,
64+
/// consider using [FutureBuilder]. Note: This future cannot be created in
65+
/// [build], as described in [FutureBuilder]'s documentation.
66+
///
67+
/// ```dart
68+
/// late Future googleFontsPending;
69+
///
70+
/// @override
71+
/// void initState() {
72+
/// super.initState();
73+
/// googleFontsPending = GoogleFonts.pendingFonts([
74+
/// ...
75+
/// ]);
76+
/// }
77+
///
78+
/// @override
79+
/// Widget build(BuildContext context) {
80+
/// return FutureBuilder(
81+
/// future: googleFontsPending,
82+
/// builder: (context, snapshot) {
83+
/// if (snapshot.connectionState != ConnectionState.done) {
84+
/// return const SizedBox();
85+
/// }
86+
/// ...
87+
/// }
88+
/// );
89+
/// }
90+
/// ```
91+
static Future<List<void>> pendingFonts([List<dynamic>? _]) =>
92+
Future.wait(pendingFontFutures);
93+
4194
/// Get a map of all available fonts.
4295
///
4396
/// Returns a map where the key is the name of the font family and the value

‎packages/google_fonts/lib/src/google_fonts_base.dart‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,25 @@ import 'google_fonts_descriptor.dart';
2727
import 'google_fonts_family_with_variant.dart';
2828
import 'google_fonts_variant.dart';
2929

30-
// Keep track of the fonts that are loaded or currently loading in FontLoader
31-
// for the life of the app instance. Once a font is attempted to load, it does
32-
// not need to be attempted to load again, unless the attempted load resulted
33-
// in an error.
30+
/// Set of fonts that are loading or loaded.
31+
///
32+
/// Used to determine whether to load a font or not.
3433
final Set<String> _loadedFonts = {};
3534

3635
@visibleForTesting
37-
http.Client httpClient = http.Client();
36+
void clearCache() => _loadedFonts.clear();
37+
38+
/// Set of [Future]s corresponding to fonts that are loading.
39+
///
40+
/// When a font is loading, a future is added to this set. When it is loaded in
41+
/// the [FontLoader], that future is removed from this set.
42+
final Set<Future<void>> pendingFontFutures = {};
3843

3944
@visibleForTesting
40-
AssetManifest assetManifest = AssetManifest();
45+
http.Client httpClient = http.Client();
4146

4247
@visibleForTesting
43-
void clearCache() => _loadedFonts.clear();
48+
AssetManifest assetManifest = AssetManifest();
4449

4550
/// Creates a [TextStyle] that either uses the [fontFamily] for the requested
4651
/// GoogleFont, or falls back to the pre-bundled [fontFamily].
@@ -107,7 +112,9 @@ TextStyle googleFontsTextStyle({
107112
file: fonts[matchedVariant]!,
108113
);
109114

110-
loadFontIfNecessary(descriptor);
115+
final loadingFuture = loadFontIfNecessary(descriptor);
116+
pendingFontFutures.add(loadingFuture);
117+
loadingFuture.then((_) => pendingFontFutures.remove(loadingFuture));
111118

112119
return textStyle.copyWith(
113120
fontFamily: familyWithVariant.toString(),
@@ -123,8 +130,8 @@ TextStyle googleFontsTextStyle({
123130
///
124131
/// Otherwise, this method will first check to see if the font is available
125132
/// as an asset, then on the device file system. If it isn't, it is fetched via
126-
/// the [fontUrl] and stored on device. In all cases, the font is loaded into
127-
/// the [FontLoader].
133+
/// the [fontUrl] and stored on device. In all cases, the returned future
134+
/// completes once the font is loaded into the [FontLoader].
128135
Future<void> loadFontIfNecessary(GoogleFontsDescriptor descriptor) async {
129136
final familyWithVariantString = descriptor.familyWithVariant.toString();
130137
final fontName = descriptor.familyWithVariant.toApiFilenamePrefix();

0 commit comments

Comments
 (0)