Flutter Apps

13.2 Themes, Splashscreens, Launcher Icons

# Themes

Theming and styling Flutter apps can be a confusing and frustrating experience which has been complicated with the changes made over the last couple years in the names of the properties that are used. To help you out, here is a repo with an incredibly horrendous global theme (opens new window) that will help you to understand the many levels of styling that every widget goes through before being rendered on the screen as an element.

Here are the layers of styling that generally take place.

  1. Your MaterialApp widget brings in the default styling properties for EVERY widget. Think of this as the collection of all the available CSS properties for every HTML element that you get in the browser. The default values are provided based on Material Design.

  2. Material design provides a global Theme widget and two variants of this called dark and light. Inside your MaterialApp widget there is a property called theme: which can be set to ThemeData.dark() or ThemeData.light(). The light version is the default one that you get even without the theme: property.

  3. If you want to properly build a professional Flutter app, then you should not just accept the default and start adding style properties inside all of your Widgets. This would be the Flutter equivalent of adding a style attribute to every HTML tag in your website. A nightmare to maintain or edit. So, instead you should create your own theme file that generates a dark ThemeData object and a light one. This will be your styling for your whole app.

// /utils/my_theme.dart
import 'package:flutter/material.dart';

class MyTheme {

  static ThemeData buildDark = () {
    final ThemeData base = ThemeData.from(
        colorScheme: const ColorScheme(
          //define your base color properties
        ),
        textTheme: const TextTheme(
          //define the text styles for the various types of text
        ),
    );

    //add the style for different widgets
    //uses the base and adds more specific default styles
    final ThemeData dark =  = base.copyWith(
      scaffoldBackgroundColor: Colors.yellow[900],
      appBarTheme: const AppBarTheme(
        //style props
      ),
      // looooooong list of different widgets with their styles
    );

    return dark;
  }
}
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
  1. The TextTheme and ColorScheme in the base will be used throughout the app. Every Text widget looks at its context to determine which level of text styling should be applied from the default TextTheme.

  2. After you have your base, we use the base.copyWith() method to add specific styling for different types of widgets. The AppBar, Card, FloatingActionButton, ListTile, and every other kind of widget have their own Theme or ThemeData class that let you set default properties for them. In here you can often override the default colours or text styles set in step 4.

  3. With all the combined style properties in your static theme, you will have a base of styling that will reduce the amount of style: properties that you need to use in your Widgets by 90% or more. But what about those one-ofs? What about the situations were we need to override the theme properties? There are a couple ways of doing this.

  • We can always add a style property inside our Text widget and use a TextStyle widget to define overriding style properties for the text. These will override the defaults, just like a style attribute in an HTML element.
  • We can use property values that start with Theme.of(context) like this style: Theme.of(context).textTheme.headline3,, to access values and use them from our defined theme. However, you must wrap your Widget inside of a Builder widget. This will go to the theme that we created in the my_theme.dart file.
  • We can wrap a widget inside a Theme widget which needs a data: property and child:. The data property needs a new ThemeData object that will contain the colorScheme and textTheme to use for your widget. The child property will be a Builder widget that has a builder: (BuildContext context) { } function. The function will return the widget(s) that you want to be styled with this new overriding ThemeData that you defined. Say that the builder function is returning a Text() widget. The Text widget will have a style property that can access the new values like this: style: Theme.of(context).textTheme.bodyText2. See the second line of text in the Card widget in the demo theme repo for an example of this.
  • If you are trying to override the specific styles from your theme for a Button, then we can override the defined theme properties like this: style: TextButton.styleFrom(). There is a styleFrom method for each of the Text, Elevated, and Outlined buttons.

# DateTime in Dart

Dart has a built-in data-type called DateTime. In the below code sample 5 of the constructor methods are show. Once you have an object of type DateTime then you have access to the properties for the parts of the date, like year, month, and day as well as the methods for outputting the DateTime object as a formatted String.


  DateTime dt1 = DateTime.now(); //current timestamp in local timezone
  print(dt1.toLocal());  // 2022-03-30 11:07:53.212

  DateTime dt2 = DateTime(2020, 12, 25); //local timezone
  print(dt2.toIso8601String());  // 2020-12-25T00:00:00.000 - note no time value

  DateTime dt3 = DateTime.utc(2020, 12, 25); // UTC timezone
  print(dt3.day);  // 25

  DateTime dt4 = DateTime.parse('1969-07-20 20:18:04Z'); //Moon landing
  print(dt4.month); // 7

  DateTime dt5 = DateTime.fromMillisecondsSinceEpoch(1600600700800);
  print(dt5.year);  // 2020
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Here is the reference for the DateTime class (opens new window) and if you go to the pub.dev (opens new window) website you can find the intl package (opens new window), which includes a DateFormat class for formatting your DateTime objects with different locales. Here is thereference to the DateFormat class (opens new window).

# Splashscreen (aka Launch Screens)

Here is the official guide to adding a SplashScreen in Flutter (opens new window). 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 (opens new window). 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 (opens new window).

The page in the Human Interface Guidelines with the different App Icon sizes (opens new window) 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 (opens new window) and here is a guide to using the flutter_launcher_icons tool (opens new window).

# RefreshIndicator

Reference for RefreshIndicator (opens new window)

When you have a scrollable container that exceeds the height of its container then it will automatically apply the physics of letting the user pull the list down further than it should be able to scroll. This action leaves a white gap at the top until the user releases the list. It is generally understood that doing this could result in a refresh of the list.

To add a RefreshIndicator to our ListView, we will make the ListView into the child: of the RefreshIndicator.

In Flutter, the way we get the refresh icon to ALWAYS appear is to add physics: const AlwaysScrollableScrollPhysics(), to the ListView and then the behaviour will be to slowly fade in a circular refresh icon. When the icon becomes fully opaque, then the onRefresh callback is triggered.

Note: The refresh indicator only works on a vertical ListView.

Scaffold(
  body: Padding(
    padding: EdgeInsets.all(16.0),
    child: RefreshIndicator(
      onRefresh: () {
        //this runs when refresh is triggered
        //call a function to do a new fetch
        //or setState()
        //or whatever you like
      },
      child: ListView.builder(
        physics: const AlwaysScrollableScrollPhysics(),
        itemCount: mylist.length,
        itemBuilder: (context, item) {
          return Text(mylist[item]);
        }
      )
    )
  )
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 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: 4/1/2022, 1:03:42 PM