Flutter Apps

13.2 Themes, Splashscreens, Launcher Icons

Module Still Under Development

# Themes

When building a Flutter app we create a ThemeData() object and set it as the value of the theme property inside the MaterialApp() for the app.

Prior to version 3.16.0 of Flutter Material Design version 2 was the default configuration used for the theming. Starting with 3.16.0 Material Design 3 became the default configuration. If you want to change from the default then you can add a boolean value for the useMaterial3 property.

MaterialApp(
  theme: ThemeData(
    useMaterial3: false,
  )
)
1
2
3
4
5

A ThemeData object contains many properties, beyond the userMaterial3 property, which together set all the theme settings for all the Widgets in your app. The three most common settings that you should define are textTheme, colorScheme, and iconThemeData.

# ColorScheme

ColorSchemes in Material Design are a set of 30 colour values from the Material Design Palette that together create your color scheme.

  • Each property in the ColorScheme class represents one color role.
  • The main accent color groups in the scheme are primary, secondary, and tertiary.
  • Colours come in pairs. Eg: Primary and onPrimary where the first value is a background colour and the on property is the text colour for that background.
  • Primary colors are used for key components across the UI, such as the FAB, prominent buttons, and active states.
  • Secondary colors are used for less prominent components in the UI, such as filter chips, while expanding the opportunity for color expression.
  • Tertiary colors are used for contrasting accents that can be used to balance primary and secondary colors or bring heightened attention to an element, such as an input field. The tertiary colors are left for makers to use at their discretion.

So, inside our MaterialApp(theme: ThemeData()), we can set the value for the colorScheme property, which will build a ColorScheme to apply to the whole app.

The default constructor for a ColorScheme object works like the following snippet. You would set a colour value for each of the Material Design color properties plus a brightness value of Brightness.dark or Brightness.light.

MaterialApp(
  //call the ColorScheme default constructor and provide all the required properties
  //there are 30+ properties that you can define. This example is just the required ones.
  colorScheme: ColorScheme(
    brightness: Brightness.light,
    primary: Colors.green.shade200,
    onPrimary: Colors.black,
    secondary: Colors.yellow.shade200,
    onSecondary: Colors.black,
    error: Colors.red,
    onError: Colors.red.shade900,
    background: Colors.white,
    onBackground: Colors.black87,
    surface: Colors.purple.shade100,
    onSurface: Colors.purple.shade900,
  ),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

There are also a couple of alternative ways to create a ColorScheme object at the top level.

// alternate approaches to building a ColorScheme
// 1. use the fromSwatch named constructor
colorScheme: ColorScheme.fromSwatch(
  primarySwatch: Colors.green,
  brightness: Brightness.light,
  accentColor: Colors.pink,
  /* and a few other props */
),

// 2. use the default colorscheme and overwrite just the color properties you want to change
colorScheme: Theme.of(context).colorScheme.copyWith(
  primary: Colors.green.shade200,
  onPrimary: Colors.black,
  secondary: Colors.yellow.shade200,
  onSecondary: Colors.black,
),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Then, inside of any object where you want to use one of your ColorScheme values we would use the Theme.of(context) approach. Here is an example, using the primary background colour as the background for a Container widget.

child: Container(
          width: 100,
          height: 100,
          color: Theme.of(context).colorScheme.primary,
        ),
1
2
3
4
5

# TextTheme

Inside the MaterialApp constructor we can create a default textTheme object which will then be applied to every Text() widget within the app. Depending on which Widget the Text() is a child of, a different one of these TextStyle properties will be applied. Material Design defines which widgets get which of the TextStyles.

MaterialApp(
  //provide all the settings to create a TextTheme object
  textTheme: TextTheme(
    displayLarge: TextStyle(fontSize: 57, /* and all the other text style properties */),
    displayMedium: TextStyle(fontSize: 45),
    displaySmall: TextStyle(fontSize: 36),
    headlineLarge: TextStyle(fontSize: 32),
    headlineMedium: TextStyle(fontSize: 28),
    headlineSmall: TextStyle(fontSize: 24),
    titleLarge: TextStyle(fontSize: 22),
    titleMedium: TextStyle(fontSize: 16),
    titleSmall: TextStyle(fontSize: 14),
    bodyLarge: TextStyle(fontSize: 16),
    bodyMedium: TextStyle(fontSize: 14),
    bodySmall: TextStyle(fontSize: 12),
    labelLarge: TextStyle(fontSize: 14),
    labelMedium: TextStyle(fontSize: 12),
    labelSmall: TextStyle(fontSize: 11),
  ),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Each of the TextStyle objects would define all the style properties like fontSize, fontWeight, color, decoration, letterSpacing, etc.

There are a couple other ways that you can create your base TextTheme to apply to the rest of your app.

//alternate approaches for creating a textTheme
// 1. a predefined TextTheme object
textTheme: Typography.blackHelsinki,

// 2. override specific properties
textTheme: Theme.of(context).textTheme.copyWith(
  //just override the ones you want to change from the default
  //if you have included the google_fonts package you can use that here
  displayMedium: GoogleFonts.lobster(
    color: Colors.red,
    fontSize: 20,
  ),
  //the GoogleFonts.lobster() method returns a TextStyle object
  //all the google_font methods do this.
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

We can access the TextTheme values in the same way we would access colour values from a ColourScheme.

child: Text('some sample text',
    style: Theme.of(context).textTheme.displayMedium,
  ),
1
2
3

# IconThemeData

The IconThemeData creates a set of properties that will apply to every Icon() that you create within your whole app. Obviously, this top level object will only include properties that can apply to every icon, not things like semanticLabel, which will only apply to a single icon.

MaterialApp(
  iconTheme: IconThemeData(
    size: 24,
    opticalSize: 48,
    opacity: 1.0,
    weight: 400,
    color: Colors.black,
  ),
)
1
2
3
4
5
6
7
8
9

If you want to access one of these properties, you can with this - Theme.of(context).iconTheme.size. However, using this inside of an Icon() would be redundant because the size property of your Icon will already be using that value from the top level.

# Overriding Properties

Many of the properties inside of ThemeData are properties specifically to create a style object for a type of Widget. An example would be appBarTheme: AppBarTheme(). Each of these widget styles contain all the style properties that would be specific to that type of Widget and those styles will be applied to every widget of that type throughout your application.

If you want to override one of those properties inside of a specific widget, then you would use Theme.of(context) to access the root themeData object value, and then target the specific theme style object you want to modify, and add a call to .copyWith(). The copyWith() method creates a copy of the style object and lets you override any of the individual properties.

Let's use AppBar widgets as an example.

In the MaterialApp(theme: ) we set up the initial values for the AppBar widget styles. This code will take all the default settings for the Material Design 3 AppBarTheme, create a copy, and then overwrite the listed values.

MaterialApp(
  theme: ThemeData(
    appDataTheme: AppBarTheme.of(context).copyWith(
      backgroundColor: Colors.orange,
      centerTitle: true,
      titleTextStyle: const TextStyle(
        color: Color.fromARGB(255, 92, 135, 93),
        fontSize: 40,
      ),
    ), //AppBarTheme
  ), //ThemeData
)
1
2
3
4
5
6
7
8
9
10
11
12

Now, any AppBar() that you create will use the appDataTheme value we created above.

If we want to overwrite ONE of our AppBar's for some reason then we can repeat the process, inside of the specific AppBar.

Scaffold(
  appBar: AppBar(
    centerText: false, //overrides the property set in the ThemeData
    title: Text('Sample Title'),
    backgroundColor: Colors.red, //overrides the property set in the ThemeData
  )
)
1
2
3
4
5
6
7

# Mapping of ColorScheme properties to Widgets

Here's an overview of how some of the color properties from ColorScheme are typically used by different widgets.

This main.dart Gist is a sample page with color values set in the ColorScheme in and with content on the page that you can rearrange to test.

  1. primary: This color is often used for elements that need to stand out, like a FloatingActionButton (FAB), CircleAvatar background, and text in a button. Also for TextField labels, and ripple effects on buttons.

  2. onPrimary: The icon on a FAB and the CircleAvatar text or icon.

  3. secondary: Applied to elements that need a contrasting color to the primary, like sliders, progress indicators, and the active link background for a navigation bar button.

  4. onSecondary: Used for elements that require a darker shade of the secondary color, similar to how primaryVariant is used. Applied to the active Icon in the navigation bar.

  5. surface: Typically used for background colors of material surfaces like AppBar, NavigationBar, Cards, ElevatedButton, dialogs, and bottom sheets.

  6. background: Represents the background color of the overall app, in other words, the MaterialApp background.

  7. error: Indicates error-related elements such as error messages, error outlines, and validation errors. If a TextField has a value for errorText then all the associated text, labels, messages, etc will be this color.

  8. tertiary: this color is there to create contrasts to balance the primary and secondary colors or to bring heightened attention to an element. In general, this color is left to the developer to apply from the color scheme.

Widget Styles

Remember that in addition to the ColorScheme property values, you have specialized theme objects that you can define in the ThemeData which can override the TextTheme or the ColorScheme defaults.

Here is a list of available specialized ThemeData properties. Note that the constructor functions are not consistent in their naming. Some end with ...Theme() and others end with ...ThemeData().

ThemeData(
  actionIconTheme: ActionIconThemeData(),
  appBarTheme: AppBarTheme(),
  buttonTheme: ButtonThemeData(),
  cardTheme: CardTheme(),
  datePickerTheme: DatePickerThemeData(),
  dividerTheme: DividerThemeData(),
  dropdownMenuTheme: DropdownMenuThemeData(),
  floatingActionButtonTheme: FloatingActionButtonThemeData(),
  iconTheme: IconThemeData(),
  inputDecorationTheme: InputDecorationTheme(),
  listTileTheme: ListTileThemeData(),
  navigationBarTheme: NavigationBarThemeData(),
  scaffoldBackgroundColor: Colors.white,
  shadowColor: Colors.black87,
  snackBarTheme: SnackBarThemeData(),
  elevatedButtonTheme: ElevatedButtonThemeData(),
  iconButtonTheme: IconButtonThemeData(),
  textButtonTheme: TextButtonThemeData(),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Splashscreen (aka Launch Screens)

Here is the official guide to adding a SplashScreen in Flutter. This can be a fairly involved process and is different for both iOS and Android.

# Launcher Icons

Here is the quick general guide to adding custom Launcher Icons in Flutter. It covers what names and locations to use when manually adding your new Launcher Icons.

The page in the Material Design guidelines with the different Launcher Icon sizes.

The page in the Human Interface Guidelines with the different App Icon sizes for iOS.

Make sure to create the proper range of sizes for your custom launcher icon.

Here is a CLI Tool for Generating your launcher icons for Flutter and here is a guide to using the flutter_launcher_icons tool.

# RefreshIndicator

This gives you access to the standard OS version of a spinner.

Reference for RefreshIndicator

# Spinners and Loaders

The Flutter_spinkit package gives you a selection of pre-built spinners that you can use when refreshing content. If you use the RefreshIndicator, discussed above, you will get the default spinner from the OS.

# What to do this week

TODO Things to do before next week

Last Updated: 12/7/2023, 10:21:37 AM