Custom font is not rendered to golden images when package is provided - flutter

I have a custom font defined in module theme. This module is a dependency in module widgets.
A widget in widgets module applies custom font like below
style: TextStyle(
fontSize: fontSize,
fontFamily: "IconActions",
package: "theme"
)
It works fine.
Unfortunately this custom font is not rendered on golden images. I have to remove the package: "theme" to fix that. But that breaks the app and the font is not displayed any more.
So basically I can have the font working correctly in the production code or the test code, but never both.
The custom font is loaded in setUp method of the test
final fontData = File('assets/fonts/IconActions.ttf')
.readAsBytes()
.then((bytes) => ByteData.view(Uint8List.fromList(bytes).buffer));
final fontLoader = FontLoader('IconActions')..addFont(fontData);
await fontLoader.load();
Am I missing something, or is it a bug?

So basically the solution is to remove package: "theme" from the TextStyle to make it work. But that's the half of the solution, because as I mentioned in the question now golden files have the font renderer correctly, but the font doesn't work in the app.
To make it work in the app we need given project structure:
pubspec.yaml (module theme)
flutter:
fonts:
- family 'ComicSans'
fonts:
- asset: packages/theme/fonts/ComicSans.ttf
widget.dart (module theme)
style: TextStyle(
fontSize: fontSize,
fontFamily: "ComicSans",
)
Now in module widgets, which is the module that contains main.dart with its main function that you run, you have to define the font again:
pubspec.yaml (module widgets)
dependencies:
flutter:
sdk: flutter
theme:
path: ../path/to/theme/module
flutter:
fonts:
- family 'ComicSans'
fonts:
- asset: packages/theme/fonts/ComicSans.ttf
Now the font is correctly displayed in both the app and the golden images.

I've had this exact issue for the last year and also couldn't get font loading to work within tests.. did not know that it was specifically the package argument that was breaking it, so thank you for updating with that result.
As for another workaround, there is a way to have the best of both worlds where you can have your standalone font package and not have to declare your packaged font files in your app that is using it.
For example, we have a company branding/typography package which we use across multiple apps that contains all our pre-configured TextStyle declarations, and another standalone package which has custom generated IconData that is stored within a *.ttf file (like FontAwesome).
The package side:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/fonts/
fonts:
- family: MyFont
fonts:
- asset: assets/fonts/MyFont.ttf
weight: 400
# etc
The packaged TextStyle:
class BrandStyles {
static const _packageName = '<package_name>';
static const headline1Style = TextStyle(
color: Colors.black,
fontFamily: 'MyFont',
fontSize: 60.0,
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
height: 1.16,
letterSpacing: 0,
package: _packageName,
);
// etc
}
Golden test
void main() {
final widget = MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
// use custom extension method to remove `package` value
headline1: BrandStyles.headline1Style.trimFontPackage(),
),
),
home: Scaffold(
body: SafeArea(child: StylesExample()),
),
);
setUp(() async {
TestWidgetsFlutterBinding.ensureInitialized();
final file = File('path/to/packaged/asset/MyFont.ttf').readAsBytesSync();
final bytes = Future<ByteData>.value(file.buffer.asByteData());
await (FontLoader('MyFont')..addFont(bytes)).load();
});
testWidgets('Golden typography test', (WidgetTester tester) async {
await tester.pumpWidget(widget);
await expectLater(
find.byType(MaterialApp), matchesGoldenFile('goldens/typography.png'));
});
}
extension StylingExtensions on TextStyle {
TextStyle trimFontPackage() {
return TextStyle(
inherit: inherit,
color: color,
backgroundColor: backgroundColor,
fontSize: fontSize,
fontWeight: fontWeight,
fontStyle: fontStyle,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
textBaseline: textBaseline,
height: height,
locale: locale,
foreground: foreground,
background: background,
shadows: shadows,
fontFeatures: fontFeatures,
decoration: decoration,
decorationColor: decorationColor,
decorationStyle: decorationStyle,
decorationThickness: decorationThickness,
debugLabel: debugLabel,
/// `replaceAll` only required if loading multiple fonts,
/// otherwise set value to your single `fontFamily` name
fontFamily: fontFamily.replaceAll('packages/<package_name>/', ''),
);
}
}
Or if like me, you have the same issue with custom icons, the same can be done within your golden test for your custom IconData with a similar extension method, removing the fontPackage value:
extension IconExtensions on IconData {
IconData convertToGolden() => IconData(
this.codePoint,
fontFamily: this.fontFamily,
);
}
Your app side
pubspec.yaml
# ...
dependencies:
flutter:
sdk: flutter
<package_name>:
git:
url: <url_to_hosted_package>.git
ref: <release_tag>
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.light().copyWith(
textTheme: TextTheme(
headline1: BrandStyles.headline1Style,
),
),
);
}
}
There is now no longer a need to declare your fonts within your apps pubspec.yaml, or even have the style package(s) within the same project/repository as your implementing app.

Related

Error: Could not find the correct Provider<LanguageChangeProvider> above this Languages Widget

I am trying to translate my app that has different screens to different languages using localizations and .arb files. The following code it shows how I implemented in MaterialApp and I only included the provider and localization in the main file, not all the pages:
The following are the package versions:
flutter_localizations:
sdk: flutter
localization: ^2.1.0
provider: ^5.0.0
Here is the LanguageChangeProvider class:
class LanguageChangeProvider with ChangeNotifier {
Locale _currentLocal = new Locale("en");
Locale get currentLocal => _currentLocal;
void changeLocalLanguage(String _localLanguage) {
this._currentLocal = new Locale(_localLanguage);
notifyListeners();
}
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<LanguageChangeProvider>(
create: (context) => LanguageChangeProvider(),
),
StreamProvider<Userid>.value(
value: AuthService()
.user, //the user is from services auth and non function stream
initialData: null,
),
],
child: Builder(builder: (context) {
return MaterialApp(
locale: Provider.of<LanguageChangeProvider>(context, listen: true)
.currentLocal,
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
color: Color(0xFF13294b),
title: 'Personal Expenses',
theme: ThemeData(
colorScheme: ColorScheme.light(
primary: const Color(0xFF13294b),
),
buttonTheme: ButtonThemeData(textTheme: ButtonTextTheme.primary),
accentColor: Colors.amber,
fontFamily: 'Quicksand',
textTheme: ThemeData.light().textTheme.copyWith(
headline6: TextStyle(
fontFamily: 'FjallaOne',
fontWeight: FontWeight.bold,
fontSize: 18,
color: Color(0xFF13294b),
),
button: TextStyle(color: Colors.white),
),
appBarTheme: AppBarTheme(
textTheme: ThemeData.light().textTheme.copyWith(
headline6: TextStyle(
fontFamily: 'FjallaOne',
fontSize: 20,
fontWeight: FontWeight.bold,
backgroundColor: Color(0xFF13294b),
),
),
),
),
home: Wrapper(),
);
}),
);
}
}
I was able to replicate your issue by removing the Builder widget that you're assigning as the child of the MultiProvider:
Then I added it again and I'm able to pull values from the LanguageChangeNotifier service down the widget hierarchy. For example, I added a property called "valueFromLanguages" in the LanguageChangeNotifier:
class LanguageChangeProvider extends ChangeNotifier {
String valueFromLanguages = "Hello from the Languages Provided Service!";
Locale currentLocal = Locale.fromSubtags();
}
then I pulled it using the Provider.of inside the Wrapper widget and I was able to see the value, as in:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(Provider.of<LanguageChangeProvider>(context).valueFromLanguages)
),
);
}
}
Here's a Gist I created real quick (I mocked a few things to make it work) but pretty much illustrates my point that the way you originally have it, it should work. Just make sure you're accessing the provided service down in the hierarchy the correct way.

Flutter assign Theme text style

I was wondering is there is a better way to assign a certain default text style of my Theme to a Text widget than this approach.
Text(
'Hello world',
style: Theme.of(context).textTheme.headline1,
),
I did assume there should be something like a separate Widget or a Text Method Text.headline1 or simply a style Command style: TextStyle.headline1.
But seems I have to go through the Theme.of(context) to get this.
Does anyone have a better solution?
I think yon can't escape some boilerplate. For me this approach looks cleanest
import 'package:flutter/material.dart';
class StyledText extends StatelessWidget {
final String text;
late final TextStyle? Function(BuildContext context)? getStyle;
StyledText.headline1(this.text, {Key? key}) : super(key: key) {
getStyle = (context) {
return Theme.of(context).textTheme.headline1;
};
}
StyledText.headline2(this.text, {Key? key}) : super(key: key) {
getStyle = (context) {
return Theme.of(context).textTheme.headline2;
};
}
// ...
#override
Widget build(BuildContext context) {
return Text(text, style: getStyle?.call(context));
}
}
And use the widget like this
StyledText.headline1("Hello world");
Theme.of returns the ThemeData value specified for the nearest BuildContext ancestor. If you don't use it, then you won't be able to access the theme configuration you may set and benefit from its advantages.
However, you can create a class called Styles where you can access the pre-defined colors, text styles and more:
class Styles {
static const Color primaryColor = Colors.blue;
static const TextStyle headline1 = TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
);
static const TextStyle bodyText1 = TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.normal,
);
}
Here is an example of using it:
Text(
'Hello world',
style: Styles.headline1,
)
You define all Your Theme Style in Main like this
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.purple,
textTheme: TextTheme(
headline1: TextStyle(
color: const Color(0xFF232323),
fontSize: 26.sp,
fontWeight: FontWeight.w500,
fontFamily: "Mont Regular",
),
),
)
Then use like this
Text("A cloud-agnostic solution for Project and HR Management",
style: Theme.of(context).textTheme.headline1)
You can use TextStyle directly:
Text(
'Hello world',
style: TextStyle(color: Colors.black, fontSize: 15.0), // Etc...
),
Theme.of(context) is a great way to go for a variety of reasons, like switching between light and dark themes. I like to create a variable for the theme and text theme to keep things clean and efficient.
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
return Column(
children: [
Text('Heading Text', style: textTheme.headline1),
Text('Caption Text', style: textTheme.caption),
Text('Body text...', style: textTheme.bodyText1),
],
);
}

Flutter and custom fonts

I have an app in flutter using different fonts from the same family, declared in pubspec.yaml like this:
fonts:
- family: Poppins
fonts:
- asset: assets/fonts/Poppins-Light.ttf
weight: 100
- asset: assets/fonts/Poppins-Medium.ttf
weight: 400
- asset: assets/fonts/Poppins-Bold.ttf
weight: 600
In my main.dart, to use Poppins as my default font for the whole app, I have declared:
theme: ThemeData(fontFamily: 'Poppins'),
However, now comes my doubts:
by declaring theme: ThemeData(fontFamily: 'Poppins'), which one of the 3 fonts is used by default?
How do I make the difference in a Text() widget when I want to use Light/Medium/Bold?.
Do I really need to declare the weight for each font type Light/Medium/Bold in pubspe.yaml?
You can use a TextTheme to define how the fonts will be used.
final TextTheme textTheme = TextTheme(
headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold, fontFamily: 'Poppins'),
headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic, fontFamily: 'Poppins'),
bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Poppins'),
)
ThemeData(textTheme: textTheme)

How to create a global TextStyle in Flutter?

In my home page in build widget before return statement I use below code.
If I create an another widget in my home page I have to copy below code before each return statement. If you create another stateless/stateful dart file I need to copy below code again.
My question is How to create a global TextStyle in Flutter? I don't want to change Theme data, all want to use my TextStyle across the my flutter project. Thanks.
TextStyle myNormalStyle = TextStyle(
fontSize: SizerUtil.deviceType == DeviceType.Mobile ? 14.0.sp : 13.0.sp,
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
color: Colors.black);
You can easily you that using by using Theme. As described on the flutter docs you can use it like :
MaterialApp(
title: title,
theme: ThemeData(
// Define the default brightness and colors.
brightness: Brightness.dark,
primaryColor: Colors.lightBlue[800],
accentColor: Colors.cyan[600],
// Define the default font family.
fontFamily: 'Georgia',
// Define the default TextTheme. Use this to specify the default
// text styling for headlines, titles, bodies of text, and more.
textTheme: TextTheme(
headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
),
)
);
or you can also use DefaultTextStyle widget to provide a default TextStyle to all the children of this widget. Read more about DefaultTextStyle.
1º create a class file:
import 'package:flutter/material.dart';
class MyTextStyle {
static const TextStyle textStyle = TextStyle(
color: Color.fromARGB(255, 247, 240, 201),
fontSize: 20,
fontWeight: FontWeight.bold,
);
}
2º import and use in your screen:
import 'package:MyApp/MyTextStyle.dart';
const Text(
'You have pushed the button this many times:',
style: MyTextStyle.textStyle,
),

Get ThemeData from static area

I am saving my Text Styles in seperate text_styles.dart file. When i want to use theme colors just like Theme.of(context).primaryColor, I cant reach ThemeData object from text_styles.dart .I solved my problem with this kind of solution but this is not good solution.
TextStyle kWelcomePageHeaderTextStyle(BuildContext context) => TextStyle(
fontFamily: "Courgette",
fontSize: 30.0,
color: Theme.of(context).primaryColor,
);
So, i need to get ThemeData from static area for use my Text Styles like this.
const kWelcomePageHeaderTextStyle = TextStyle(
fontFamily: "Courgette",
fontSize: 30.0,
color: [THEME_DATA_OBJECT_NEEDED].primaryColor,
);
Can I get ThemeData object from text_styles.dart or is there any better solution?
I found better solution with Dependency Injection. I registered the dependency which is BuildContext in MaterialApp.
void main() {
final GetIt sl = GetIt.instance;
runApp(MaterialApp(
theme: myLightTheme,
darkTheme: myDarkTheme,
builder: (BuildContext context, Widget widget) {
if (!sl.isRegistered<BuildContext>()) {
sl.registerSingleton<BuildContext>(context);
}
return HomePage();
},
));
Then I can get Theme on the static area
const kWelcomePageHeaderTextStyle = TextStyle(
fontFamily: "Courgette",
fontSize: 30.0,
color: Theme.of(sl.get<BuildContext>()).primaryColor,
);
Your app does not have a single, globally available theme. So you cannot get it.
Your app already has two themes out of the box (dark mode/light mode) and you can have many more. You can even have a different theme for a specific subtree in your build methods using the Theme widget. You can read more about it in the documentation.
Getting the Theme from the context is the preferred method.