16 From Dark Mode on How to Customize Different Styles of App Themes

16 From Dark Mode On How to Customize Different Styles of App Themes #

Hello, I’m Chen Hang. Today, I’m going to share with you the topic of customizing different styles of app themes, starting from night mode.

In the previous article, I introduced two ways of customizing widgets: assembly and painting. For assembly, we decompose the target view in a top-down, left-to-right layout order and encapsulate basic widgets into Columns and Rows to create higher-level widgets. As for painting, we use a CustomPainter as the carrier for drawing logic, and in its paint method, we use a Paint brush and a Canvas to draw different styles and types of graphics, thus achieving custom components based on painting.

For a product, in the early stages of development, the focus is more on the presence or absence of basic functionality: engineers are responsible for implementing the functionality, while product managers assess its usability. Once the basic functionality of a product is well-established, reaching a score of around 60 to 70, further growth requires the involvement of operations.

Among these, implementing app personalization through user segmentation is a common growth operation technique, and changing the theme style is an important technical means of achieving personalization.

For example, Weibo, UC Browser, and e-book clients all support night mode, while e-commerce applications like Taobao and JD.com automatically update their theme styles on specific e-commerce event days. Even today’s mobile operating systems offer the ability to switch and display styles at the system level.

So, how are these in-app style switching features implemented? In Flutter, what needs to be done to add theme switching functionality to a regular app? I will share the answers to these questions in detail in today’s article.

Theme Customization #

A theme, also known as a skin or color scheme, is typically composed of colors, images, font sizes, and fonts. We can consider it as a collection of visual resources for different scenarios, along with their corresponding configurations. For example, the buttons in an app require background images, font colors, and font sizes, regardless of the scenario. The so-called theme switching is simply the update of these resources and configurations between different themes.

Therefore, in app development, we usually do not focus on whether the visual effects of resources and configurations look good or not, but only whether the visual functions provided by the resources can be used. For example, for image resources, we do not need to be concerned about the actual effect of rendering. We only need to ensure that it renders as a fixed-size area without affecting the page layout and can run through the business process.

Visual effects are prone to change. We abstract these changing parts and classify the resources and configurations that provide different visual effects according to themes, integrating them into a unified middleware layer. This allows us to manage and switch themes.

In iOS, we usually write the configuration information of themes to a plist file in advance and control which configuration the app should use through a singleton. In Android, the configuration information is written into the xml of various style attributes. We can switch themes by using the setTheme method of an activity. Front-end development follows a similar approach, where switching between multiple themes/color schemes can be achieved simply by changing the CSS.

Flutter also provides similar capabilities, with the management of theme configuration information unified under ThemeData.

ThemeData covers the customizable parts of the Material Design specification, such as the brightness of the app, primaryColor, accentColor, fontFamily for text, cursorColor for input fields, etc. If you want to learn more about other API parameters for ThemeData, you can refer to the official documentation ThemeData.

By customizing the application’s theme through ThemeData, we can achieve style switching at both the global level of the app and the local level of a widget. Next, I will explain these two types of theme switching separately.

Global Unified Visual Style Customization #

In Flutter, the initialization method of the MaterialApp class provides us with the ability to set the theme. We can use the theme parameter to change the app’s primary color, font, and other settings to customize the display style of the interface under MaterialApp.

The following code demonstrates how to set the global theme of the app. In this code, we set the brightness of the app to dark and the primary color to cyan:

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
      brightness: Brightness.dark,
      primaryColor: Colors.cyan,
  ),
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

Try running it, and the effect is as follows:

Figure 1 Flutter global mode theme

As you can see, although we only modified the primary color and brightness, the color of buttons and text also changed accordingly. This is because by default, many other secondary visual properties in ThemeData are affected by the primary color and brightness. If we want to have precise control over their display style, we need to further refine the theme configuration.

In the following example, we adjust the color of icons to yellow, the color of text to red, and the color of buttons to black:

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
      brightness: Brightness.dark,
      accentColor: Colors.black,
      primaryColor: Colors.cyan,
      iconTheme: IconThemeData(color: Colors.yellow),
      textTheme: TextTheme(body1: TextStyle(color: Colors.red)),
  ),
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

Run it, and you will see that the color of icons, text, and buttons all changed accordingly.

Figure 2 Flutter global mode theme example 2

Customizing Visual Style Locally #

Although it is necessary to provide a unified visual presentation for the entire app, sometimes we want to set a different display style for a specific page or section that is different from the app’s style. Taking the theme switching feature as an example, we want to provide different display previews for different themes.

In Flutter, we can use Theme to override the app theme locally. Theme is a singleton widget container, similar to MaterialApp. We can customize the style of its child widgets by setting its data property:

  • If we don’t want to inherit any global colors or font styles from the app, we can directly create a ThemeData instance and set the corresponding styles.
  • If we don’t want to override all styles locally, we can inherit the app’s theme and use the copyWith method to update only specific styles.

The following code demonstrates these two usage scenarios:

// Creating a new theme
Theme(
    data: ThemeData(iconTheme: IconThemeData(color: Colors.red)),
    child: Icon(Icons.favorite)
);

// Inheriting the theme
Theme(
    data: Theme.of(context).copyWith(iconTheme: IconThemeData(color: Colors.green)),
    child: Icon(Icons.feedback)
);

Theme overriding example

Figure 3. Example of overriding the theme locally

In the above example, since Theme only has one Icon widget as its child, both approaches can achieve the goal of overriding the global theme to change the style of the icon. This way of using a local theme to override the global theme is a common method in Flutter to customize the display style of child widgets.

In addition to defining customizable styles from the Material Design specification, another important use of themes is style reuse.

For example, if we want to reuse the title style from the Material Design specification for a piece of text, or reuse the app’s primary color for the background color of a child widget, we can use the Theme.of(context) method to retrieve the corresponding properties and apply them to the style of the text.

The Theme.of(context) method searches up the widget tree and returns the nearest theme in the widget tree. If the parent widgets have a separate theme defined, that theme is used. Otherwise, the app’s global theme is used.

In the following example, we create a Container widget that wraps a Text widget. In the style definition of the Text widget, we reuse the global title style, and in the background color definition of the Container, we reuse the app’s primary color:

Container(
    color: Theme.of(context).primaryColor, // Reuse app's primary color as the background color of the container
    child: Text(
      'Text with a background color',
      style: Theme.of(context).textTheme.title, // Reuse app's text style for the text component
    ));

Theme reuse example

Figure 4. Example of theme reuse

Customizing Themes for Different Platforms #

Sometimes, in order to meet the needs of users on different platforms, we want to set different styles for specific platforms. For example, on the iOS platform, we want to set a light theme, while on the Android platform, we want to set a dark theme. In the face of such requirements, we can determine the current platform where the application is running based on defaultTargetPlatform, and set the corresponding theme according to the system type.

In the example below, we have created two themes for iOS and Android respectively. In the initialization method of MaterialApp, we set different themes based on the platform type:

// Light theme for iOS
final ThemeData kIOSTheme = ThemeData(
    brightness: Brightness.light, // Light theme
    accentColor: Colors.white, // Foreground color (buttons) is white
    primaryColor: Colors.blue, // Theme color is blue
    iconTheme: IconThemeData(color: Colors.grey), // Icon theme is gray
    textTheme: TextTheme(body1: TextStyle(color: Colors.black)) // Text theme is black
);

// Dark theme for Android
final ThemeData kAndroidTheme = ThemeData(
    brightness: Brightness.dark, // Dark theme
    accentColor: Colors.black, // Foreground color (buttons) is black
    primaryColor: Colors.cyan, // Theme color is cyan
    iconTheme: IconThemeData(color: Colors.blue), // Icon theme color is blue
    textTheme: TextTheme(body1: TextStyle(color: Colors.red)) // Text theme color is red
);

// Application initialization
MaterialApp(
  title: 'Flutter Demo',
  theme: defaultTargetPlatform == TargetPlatform.iOS ? kIOSTheme : kAndroidTheme, // Select different theme based on platform
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

Try running it:

(a) iOS platform

(b) Android platform

Figure 5 Setting corresponding theme based on different platforms

Of course, in addition to themes, you can also use the defaultTargetPlatform variable to implement other logic that needs to determine the platform, such as using components that are more in line with the Android or iOS design style on the interface.

Conclusion #

Alright, that’s it for today’s sharing. Let’s briefly review the main content of today.

Theme setting is an advanced feature of app development, which essentially provides a management mechanism for visual resources and visual configuration. Similar to other platforms, Flutter also provides a centralized management mechanism for themes, where customizable styles can be defined in ThemeData that follows the Material Design specifications.

We can achieve a unified visual style for the entire application by setting the MaterialApp’s global theme. Additionally, we can use the Theme widget container to override the global theme with a local theme to achieve a locally independent visual style.

In addition, during the process of customizing components, we can use the Theme.of method to retrieve the corresponding property values of the theme, thereby achieving the reuse of various components in terms of visual style.

Finally, when facing common scenarios of setting themes for different platforms, we can accurately identify the current system of the application based on defaultTargetPlatform, and configure the corresponding theme accordingly.

Thinking exercise #

Finally, I’ll leave you with a small homework assignment.

In the previous article, I introduced how to implement custom UI layout for the App Store upgrade item. Now, please add a switch night mode functionality to this custom widget.

Feel free to leave a comment in the comment section to share your thoughts. I will be waiting for you in the next article! Thank you for listening, and feel free to share this article with more friends to read together.