A deep dive into the notion of Theme and more specifically to the notion of Text Styles.

Difficulty: Intermediate


Introduction

Working with the notion of Theme is something very important if you want to ensure that your application will have a consistent and coherent layout throughout all the different screens.

Many times I have seen applications (and I also made this error in the past) with different styles from one page to another. The main reason comes from the fact that it is very easy to create a style on the fly, using the TextStyle class and apply it to a Text widget. Very soon, this leads to having potentially inconsistent layouts.

In order to manage this in a better way, I have also seen cases where developers created a helper class wherein they defined a series of TextStyle like this:

class TextStyleHelper {
    const TextStyle title = TextStyle(color: Colors.blue, fontSize: 18.0, fontWeight: FontWeight.bold,);
    const TextStyle subTitle = TextStyle(color: Color(0xFF999999), fontSize: 16.0, letterSpacing: 1.2);
    const TextStyle description = TextStyle(color: Colors.black87, fontSize: 14.0,);
}

…and in order to use it, were referencing them as follow:

    Container(
        child: Text('My Title', style: TextStyleHelper.title,),
    ),

This is already better, however, you need to make sure that the WHOLE application refers to these styles, defined in the TextStyleHelper class.

On top of this, all the Flutter Framework Widgets that deal with texts are using the notion of Theme(s) to create an implementation harmony. The fact of deviating from this could cause some incoherence in the layout and some discomfort for your application users.

Finally, most Flutter Applications are now using more and more external packages, either written by the Flutter team itself or, by individuals. Try to imagine if each and every package starts defining its own styles without respecting any convention… This would become anarchy in terms of the layout!

Therefore, it is highly encouraged to use the notion of Theme which is entirely configurable.

This article tries to explain how to use it and will show you a way I use to centralize my themes.


Background information

Before going into the details, let’s maybe first introduce some basic notions, which will be used later in this article.

Terminology

Please refer to this article for a more complete explanation related to Typography.

Glyphs

In typography, a glyph is an elemental symbol within an agreed set of symbols, intended to represent a readable character for the purpose of writing (ref: wikipedia).

In very simple words, a glyph is a character (a, b, D, W…), a figure (0, 1, 2…), a punctuation (; ! .), a symbol ($ £ @) … a single piece of a text to display on your screen.

Font

In our IT world, a Font could be understood as a digital file containing the definition of a set of graphically related glyphs with specific size, weight and style. It defines the shape, size and graphics of each of its glyphs.


Font Size

The font size is expressed in logical pixels (Android: sp, iOS: pt, Web: rem) and defines the size of the glyphs.

Please note that there is no conversion ratio between ‘sp’ (Android) and ‘pt’ (iOS), while there is a conversion ratio for the Web. This conversion ratio is 0.0625. As an example, a fontSize of 10sp (Android) will be 10pt (iOS) and 0.625rem (Web).

In Flutter, the default font size is set to 14.

A same value of font size can give different layouts, depending on the font being used!

Each font defines its own reference size, called ‘em', based on which each glyph is designed.

As a consequence, it is not unusual to have a different layout using 2 different fonts, even setting the same font size!

For more information related to this, check out this article.


Baseline

The baseline is an invisible line upon which a line of text rests. To visualize what is the baseline, consider a copybook you used at school and you have to write your text, taking a horizontal line as a reference to draw your letters. The baseline is that horizontal reference line of your copybook.

The baseline is a very important notion in Material Design, as it is used to measure the vertical distance between text and an element.


Letter-Spacing

The letter-spacing refers to the space to insert between each letter (glyph) in a piece of text.


Weight

The weight refers to the thickness of the font’s stroke. Common weights are:

  • light
  • regular
  • medium
  • bold

However, there might exist additional ones and Flutter defines the following ones:

  • FontWeight.w100 (the least thick)
  • FontWeight.w200 (extra-light)
  • FontWeight.w300 (light)
  • FontWeight.w400 (normal / regular / plain)
  • FontWeight.w500 (medium)
  • FontWeight.w600 (semi-bold)
  • FontWeight.w700 (bold)
  • FontWeight.w800 (extra-bold)
  • FontWeight.w900 (the most thick)

TextSpan

In Flutter, when you are using the Text widget, the latter is rendered as a TextSpan (in a RichText widget). A span is an amount of space that has dimensions.


Height

In Flutter, the height (textStyle.height) defines a ratio to be applied to the font size to give the exact line-height of the TextSpan which renders the text.

It has to be noted that each font defines its own “font metrics default height". This also explains why the height of a TextSpan may also differ from one font to another, even setting the same font height.

The following picture illustrates the variations you can observe, using different fonts, setting the very same fontSize. Each row of that picture gives the outcome defining the height as follows:

  • row 1: height is not defined

    the TextSpan height corresponds to the “font metrics default height

  • row 2: height is 1

    the TextSpan height corresponds to exactly 1.0 * fontSize

  • row 3: height is 0.8

    the TextSpan height corresponds to exactly 0.8 * fontSize

Variations based on Font and height
Variations based on Font and height

The code to produce this is the following:

Variations based on Font and height (code)
Variations based on Font and height (code)

This image illustrates quite well the notions I tried to explain here above:
  • each font has a different box height, even using the same fontSize parameter;
  • each font has a different baseline. None of the letters are “touching” the same horizontal line

This leads to the following initial conclusion:

The use of several fonts might lead to inconsistent alignments and a lack of harmony of the layout.


Notion of TextTheme

Flutter defines a series of typographical text styles that are referenced by the different Widgets that use the notion of Text.

These text styles (Typography 2018) are the following:

English-likeChinese, Japanese, KoreanArabic, Farsi, Hindi, Thai
NameSizeWeightLSSizeWeightSizeWeight
headline196w300-1.596w10096w400
headline260w300-0.560w40060w400
headline348w4000.0 48w40048w400
headline434w4000.2534w40034w400
headline524w4000.0 24w40024w400
headline620w5000.1521w50021w700
subtitle116w4000.1517w40017w400
subtitle214w5000.1015w50015w500
bodyText116w4000.5 17w40017w700
bodyText214w4000.2515w40015w400
button 14w5000.7515w50015w700
caption 12w4000.4 13w40013w400
overline 10w4001.5 11w40011w400
(*) LS stands for LetterSpacing



The following picture gives you an overview of these text styles for “English-like".

2018 Material TextStyles
2018 Material TextStyles

Side note

As of version 1.13.8 of Flutter, the names that were used in the 2014 version of the Material Design, were deprecated. This explains why you might notice a lot of warnings with some previous code.

So far, the old naming still works as an internal mapping is ensured, but I would recommend that you move to the new namings as soon as possible.

However, the default typography which is used when initializing the ThemeData is still 2014 ?!? I suppose this will be fixed shortly.

Besides the physical characteristics of the glyphs (fontSize, fontWeight, letterSpacing), we also need to consider the default colors, which are defined based on the platform type and brightness. The following table gives the “names” of the corresponding standards:

PlatformiOS / macOSAndroid / FuchsiaWindowsLinux
BrightnesslightCupertinoMountainViewRedmondHelsinki
dark

The following table gives the corresponding default colors and font families:

StylesBrightnessfontFamily
DarkLightCupertinoMountainViewRedmondHelsinki
headline1black54white70.SF UI DisplayRobotoSegoe UIRoboto
headline2black54white70
headline3black54white70
headline4black54white70
headline5black87white
headline6black87white
subtitle1black87white.SF UI Text
subtitle2blackwhite
bodyText1black87white
bodyText2black87white
buttonblack87white
captionblackwhite70
overlineblack54white

When to use them?

Here is a short summary of the recommendations made by Material Design.

Headlines

There are 6 headlines (headline1 to headline6) and they are the largest text to put on the screen.

They should be reserved for short and important texts such as:

  • titles
  • numerals

Subtitles

There are 2 subtitles (subtitle1 and subtitle2) and are smaller than headlines.

They should be reserved for medium-emphasis texts but still relatively short texts.

BodyText

There are 2 body types (bodyText1 and bodyText2). They are smaller than subtitles and should typically be used for longer texts, such as description.

Caption & Overline

Caption and overline (text with a line above it) are the smallest font sizes.

They should be reserved to annotate imagery or to introduce a headline.

Button

Button text should be reserved for a call to actions, such as:

  • button
  • tabs
  • navigation bars

The following picture shows an example:

Example of use
Example of use

How does Flutter compose its TextThemes?

Please be reassured, I will not go into all the details this time but, will remain focused on the main aspects.

When you start your application, everything begins as soon as you define the MaterialApp.

The most important parameters related to the theme that can be defined at the level of the MaterialApp widget are:

  • theme

    Defines the (usually) light theme definition.

  • themeDark

    Often forgotten but available, defines a second theme to provide a dark version of the user interface. This themeDark can also be used for platforms that allow the user to select a system-wide ‘dark mode’.

  • themeMode

    This parameter determines which of the 2 themes to use [default: ThemeMode.system]. This means that the theme to be used will depend on the user settings.

    However, it is possible to force the theme, independently of the user settings, forcing it to ThemeMode.light or ThemeMode.dark.

  • fontFamily

    This parameter defines the default fontFamily to be used, if omitted in any definition.

  • typography

    This parameter (default: Typography.material2014) is used as a reference to define the textTheme, primaryTextTheme and accentTextTheme.

  • locale & localizationsDelegates

    Usually forgotten in terms of impact on the textTheme(s), these parameters are very important as they define variations in terms of baseline, fontSize, fontWeight (see later).


How does Flutter determine the ‘theme’ to use?

Here is the extract of the source code (I just changed the last line to ease the reading), where widget refers to the MaterialApp instance:

final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
ThemeData theme;

if (widget.darkTheme != null) {
  final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
  if (mode == ThemeMode.dark ||
      (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark)) {
    theme = widget.darkTheme;
  }
}

theme ??= widget.theme ?? ThemeData(brightness: Brightness.light);

So, Flutter invokes the ThemeData factory which initializes some textStyles:

  • 3 main TextTheme:

    • textTheme
    • primaryTextTheme
    • accentTextTheme
  • 19 indirect ones:

Namedefault
toggleButtonsTheme.textStyletextTheme.bodyText2
inputDecorationTheme.labelStyletextTheme.subtitle1
inputDecorationTheme.helperStyletextTheme.caption
inputDecorationTheme.hintStyletextTheme.caption
inputDecorationTheme.errorStyletextTheme.caption
inputDecorationTheme.prefixStyletextTheme.caption
inputDecorationTheme.suffixStyletextTheme.caption
inputDecorationTheme.counterStyletextTheme.caption
sliderTheme.valueIndicatorTextStyletextTheme.bodyText1
tabBarTheme.labelStyletextTheme.bodyText1
tabBarTheme.unselectedLabelStyletextTheme.bodyText1
tooltipTheme.textStyletextTheme.bodyText2
chipTheme.labelStyletextTheme.bodyText1
chipTheme.secondaryLabelStyletextTheme.bodyText1
navigationRailTheme.unselectedLabelTextStyletextTheme.bodyText1
navigationRailTheme.selectedLabelTextStyletextTheme.bodyText1
snackBarTheme.contentTextStyletextTheme.subtitle1
popupMenuTheme.textStyletextTheme.subtitle1
bannerTheme.contentTextStyletextTheme.bodyText2

The ‘cupertinoOverrideTheme’ is a special one that is aimed at overwriting when using the Cupertino Widgets.

Wow! So many TextStyles

What is the difference between ‘textTheme’, ‘primaryTextTheme’ and ‘accentTextTheme’?

When considering the default definition, there is no differences in terms of fontSize, fontFamily, height, fontWeight, letterSpacing, decoration…

Differences could be set when there is a need that the text contrasts with its container. Most of the time, this will be limited to the notion of colors.


Where are they used by the different Flutter Framework Widgets?

Some time ago, in order to better understand how and when these pre-defined TextStyles were used by the Flutter Framework, I started up reading the whole source code and made the inventory. Here is my personal Excel Cheat Sheet that gives a summary of the way TextStyles are used by the main Flutter Widgets (version 1.17):

TextStyle Excel Cheatsheet

Click to download

At a glance, we directly see that headline1 is never used, headline(2-5) are only used by the Dialogs (TimePicker, DatePicker, About…) and the most used ones are headline6 (former: title), subtitle1, bodyText1, bodyText2, caption and of course, button.


Warning... hardcoded values or rules...
Despite the fact that the Flutter team tried as much as possible to leave full freedom to the developers to define their own themes, the current Flutter Framework source code, unfortunately, still contains a couple of hardcoded values and rules. I tried to list them up in the Excel spreadsheet.

What about the user-defined Text Theme(s)?

So far, we have seen the principles of the TextTheme as defined by Flutter, based on the Material Design standard. Does this mean that we may not apply any changes?

Not at all. However, based on the way the text themes are used across all Flutter Framework Widgets, it is becoming obvious that:

If you want to harmonize your layout, you should also use as much as possible the following style names inside your code:

  • headline6
  • subtitle1
  • subtitle2
  • bodyText1
  • bodyText2
  • caption
  • button

Therefore, how can we define our own TextStyles without breaking the rules and harmony?

Rule 1: Be aware of the impacts…

When you define your application theme, be aware that your definition will be used by other Widgets!

For example, if set the textTheme.subtitle1, you have to remember that this will potentially impact the following Widgets:


  • TimePicker
  • AlertDialog
  • PaginatedDataTable
  • CircleAvatar
  • DropdownButton
  • ExpansionTile
  • GridTitleBar
  • InputDecorator
  • ListTile
  • PopupMenu
  • SnackBar
  • TextField
  • TextFormField
  • …and all derived Widgets !

This is where my Excel Cheatsheet might be useful!

Rule 2: Use the Theme.of(context) in your Widgets

When you define your own Widgets, do not forget to use the theme.of(context) to reference an existing textStyle.

For example, suppose that you are building a new Cartouche Widget which needs to display a title, a picture and a label.

A bad practice would be to write it without considering the existing styles and to write it like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Cartouche extends StatelessWidget {
  const Cartouche({
    Key key,
    this.title,
    this.caption,
    this.assetImage,
    this.size,
    this.padding = const EdgeInsets.all(8.0),
  }) : super(key: key);

  final String title;
  final String caption;
  final String assetImage;
  final Size size;
  final EdgeInsets padding;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Container(
        width: size.width,
        height: size.height,
        padding: padding,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              title,
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold, letterSpacing: 0.15),
            ),
            Container(width: size.width - padding.horizontal * 2.0, child: Image.asset(assetImage)),
            Text(
              caption,
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 10.0, letterSpacing: 0.4),
            ),
          ],
        ),
      ),
    );
  }
}

A better approach would be to re-use the existing definition as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Cartouche extends StatelessWidget {
  const Cartouche({
    Key key,
    this.title,
    this.caption,
    this.assetImage,
    this.size,
    this.padding = const EdgeInsets.all(8.0),
  }) : super(key: key);

  final String title;
  final String caption;
  final String assetImage;
  final Size size;
  final EdgeInsets padding;

  @override
  Widget build(BuildContext context) {
    
    final TextTheme textTheme = Theme.of(context).textTheme;

    return Card(
      child: Container(
        width: size.width,
        height: size.height,
        padding: padding,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              title,
              textAlign: TextAlign.center,
              style: textTheme.subtitle1.copyWith(fontWeight: FontWeight.bold),
            ),
            Container(width: size.width - padding.horizontal * 2.0, child: Image.asset(assetImage)),
            Text(
              caption,
              textAlign: TextAlign.center,
              style: textTheme.caption,
            ),
          ],
        ),
      ),
    );
  }
}

Explanation:

  • line #20: we retrieve the current TextTheme
  • line #33: we use the regular subtitle1 theme and we force it to bold
  • line #39: we use the regular caption theme

This ensures that if you change the theme definition at a later stage, you will keep the harmony.

Rule 3: Consider creating a dedicated Theme for a new package

Let’s suppose that you want to create a new package (let’s say a new RadialMenu).

As the latter will (most certainly) not be part of the regular Flutter Framework and might require very specific layout settings, it might be a very good idea to create a specific theme for this package. This will give the possibility to developers to customize it, without taking any risks of impacting the others.

Of course, it will be highly recommended to consider fallback values, based on the Material Design standards.

As an example, you could write something like:

class RadialMenuTheme extends InheritedTheme {
  const RadialMenuTheme({
    Key key,
    @required this.data,
    @required Widget child,
  }) : super(key: key, child: child);

  final RadialMenuThemeData data;

  ...
}

class RadialMenuThemeData {
  const RadialMenuThemeData({
    this.menuTextStyle,
    ...
  });

  final TextStyle menuTextStyle;
  ...
}

Then to use it (and let’s make the assumption that the developer will apply whatever style to it):

@override
Widget build(BuildContext context){
  return RadialMenuTheme(
    data: RadialMenuThemeData(
      menuTextStyle: TextStyle(fontWeight: FontWeight.w500),
    ),
    child: RadialMenu(
      menuColor: Colors.red,
    ),
  );
}

And, at rendering time, at the level of the RadialMenu build(), you might consider something like:

@override
Widget build(BuildContext context){
  final ThemeData theme = Theme.of(context);
  final RadialMenuThemeData radialMenuThemeData = RadialMenuTheme.of(context) ?? RadialMenuThemeData();
  TextStyle menuTextStyle = radialMenuThemeData.menuTextStyle ?? theme.textStyle.subtitle1;

  if (widget.menuColor != null){
    menuTextStyle = menuTextStyle.copyWith(color: widget.menuColor);
  }

  ...
}

Of course, this is not a full example but I hope it gives you an idea of a way of handling the themes.


What about the TextStyle() class?

Does this all mean that you should proscribe the use of the TextStyle class?

Not at all but, as a recommendation, I would suggest to:

  • try to refrain yourself from using TextStyle in an anarchical way.

  • try to limit its use to the definition of

    • colors
    • decoration
    • weight
  • try to avoid hardcoding

    • fontSize
    • fontFamily
    • letterSpacing

Is it possible to extend the ThemeData ?

As the ThemeData is part of the formal classes of the Flutter Framework, it is only managed by the Flutter Team and therefore, it is not allowed but nothing prevents you from creating your own ApplicationExtendedTheme, which could then contain whatever you want!

Where should it be located?

The Theme widget is a child of your MaterialApp and because the theme definition also depends on the locale (remember? the textStyles are not the same for Farsi, Arabic, English…), but also because it would be great to base your ApplicationExtendedTheme on the ThemeData definition, it is better to also have it inside the MaterialApp subtree.

MaterialApp Widget has a very convenient builder property that could be used for this purpose. The signature of this function is:


Widget Function(BuildContext context, Widget child)

The child argument corresponds to whatever needs to be rendered underneath (typically your screens).

So we now know where to put our ApplicationExtendedTheme widget.

How to build it?

As the objective is to make it available to all widgets of your application, it would be great to be able to retrieve it, using the usual “of(context)” statement.

Part 1: The ApplicationExtendedTheme Widget

Therefore, we will build it as a StatelessWidget, which will contain an InheritedTheme to hold the theme extra definition.


class ApplicationExtendedTheme extends StatelessWidget {
  const ApplicationExtendedTheme({
    Key key,
    this.data,
    @required this.child,
  })  : assert(child != null),
        super(key: key);

  final ApplicationExtendedThemeData data;
  final Widget child;

  static ApplicationExtendedThemeData of(BuildContext context) {
    final _InheritedTheme inheritedTheme = 
                context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
    return inheritedTheme?.theme?.data ?? ApplicationExtendedThemeData.fromContext(context);
  }

  @override
  Widget build(BuildContext context) {
    return _InheritedTheme(
      theme: this,
      child: child,
    );
  }
}

Explanation:

As we can see, this Widget allows storing the definition of the ApplicationExtendedThemeData if provided.

The of function returns it (if available) or requests its composition based on the BuildContent if none could be found.

Part 2: The underlying InheritedTheme Widget

The second internal Widget is there to hold the ApplicationExtendedThemeData for later re-use or to request (and then store) the data to be produced, if not existing or different.


class _InheritedTheme extends InheritedTheme {
  const _InheritedTheme({
    Key key,
    @required this.theme,
    @required Widget child,
  })  : assert(theme != null),
        super(key: key, child: child);

  final ApplicationExtendedTheme theme;

  @override
  Widget wrap(BuildContext context, Widget child) {
    final _InheritedTheme ancestorTheme = context.findAncestorWidgetOfExactType<_InheritedTheme>();
    return identical(this, ancestorTheme) 
      ? child 
      : ApplicationExtendedTheme(data: theme.data, child: child);
  }

  @override
  bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
}
Part 3: The ApplicationExtendedThemeData class

Here is an example of such a class, which generates 2 fake text styles.


class ApplicationExtendedThemeData with Diagnosticable {
  ApplicationExtendedThemeData();

  //
  // Special style for Buttons which are
  // disabled and present in an AppBar
  //
  TextStyle appBarDisabledButtonTextStyle;

  //
  // Special style for messages where
  // the user is requested to take
  // an important decision
  TextStyle userImportantDecisionMessageTextStyle;

  //
  // Builds the Extended Themes, based on the currently defined based Themes
  //
  factory ApplicationExtendedThemeData.fromContext(BuildContext context) {
    final ThemeData themeData = Theme.of(context);

    final ApplicationExtendedThemeData theme = ApplicationExtendedThemeData();

    //
    // (Fake) Define the AppBar button text styles
    //
    theme.appBarDisabledButtonTextStyle = themeData.textTheme.button
        .copyWith(
          fontStyle: FontStyle.italic,
        )
        .apply(
          decoration: TextDecoration.lineThrough,
          fontSizeFactor: 0.8,
        );

    //
    // (Fake) Define the message style
    //
    theme.userImportantDecisionMessageTextStyle = themeData.primaryTextTheme.bodyText1
        .copyWith(
          fontWeight: FontWeight.w800,
        )
        .apply(
          fontSizeFactor: 1.2,
          decoration: TextDecoration.underline,
        );

    return theme;
  }

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is ApplicationExtendedThemeData &&
        other.appBarDisabledButtonTextStyle == appBarDisabledButtonTextStyle &&
        other.userImportantDecisionMessageTextStyle == userImportantDecisionMessageTextStyle;
  }

  @override
  int get hashCode => hashList([
        appBarDisabledButtonTextStyle,
        userImportantDecisionMessageTextStyle,
      ]);
}

As you can see, these 2 fake styles are based on the existing definition of the general theme and are, therefore, coherent from a layout harmony perspective.

Part 4: Inject the ApplicationExtendedThemeData via the ApplicationExtendedTheme

As previously said, this will be done at the level of the MaterialApp as the following extract shows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
return MaterialApp(
  title: 'Material Design Text demo',

  //
  // Theme
  //
  theme: ThemeData(
    // general definition, here
  ),

  //
  // Insert the ApplicationExtendedTheme
  //
  builder: (BuildContext context, Widget child){
    return ApplicationExtendedTheme(
      child: child,
    );
  },

  //
  // Starting page
  //
  home: SplashPage(),
);
Part 5: How to use it?

Finally, the way of using it is very similar to the regular Theme.

@override
Widget build(BuildContext context) {
  final ApplicationExtendedThemeData applicationExtendedThemeData 
              = ApplicationExtendedTheme.of(context);

  return RaisedButton(
    child: Text(
      'This is my button',
      style: applicationExtendedThemeData.appBarDisabledButtonTextStyle,
    ),
    onPressed: (){
      ...
    },
  );
}

Really necessary?

Is it really necessary to do all this, will you ask me?

Believe me, if you need to develop a huge project and get back to it after quite some time, the fact of having all the Application Extended Themes in one single location will greatly help you.

The example I gave here above is very basic and limited to 2 fake TextStyles. In practice, I also sometimes define Application Specific Themes and I embed them to the ApplicationExtendedThemeData class and this is very convenient!


Conclusions

I really hope that this quick tour of the notion of TextStyle through the use of the Theme gives you an idea of the importance of using the notion of themes and also demystified it.

The ApplicationExtendedThemeData is a personal way I found to centrally manage the application styles and make them coherent with the theme definitions. It helps me reduce drastically the uncontrolled styles and, forces me to have them all defined in the same file. There are other ways of doing it, but this is the one I personally use.

Stay tuned for new articles, shortly and, as usual, happy coding!