An ideal theme structure
We have a dark theme, a light theme, as well as a light theme with a dark toolbar.
It turns out that we have more than three topics – we have 22 topics and the topic hierarchy looks like this. Each square represents a topic, in which the arrows show how the topics inherit each other, but when we examine the usage, it’s not clear why a screen is used. one particular variation versus another and some use them all:
This application is growing rapidly. At the last count, we had about 130 activities and 300 fragments, developed by 18 Android engineers, spread across multiple teams releasing a new release, every week, so we could understand how We come to this position.
The hardest thing is we have two trees. This means it is easy to add a topic related error to a tree rather than another tree, and the same thing happens with error correction.
What we want is something closer to the structure presented:
There is a single tree with four layers:
- app theme
- base theme
- platform theme
- framework theme
App theme
At the bottom are the application topics we apply at the activity level:
Theme.Monzo.Light
Theme.Monzo.Dark
The main application theme will contain values for color attributes, like colorPrimary
, colorSurface
and android:colorBackground
. For example, android:colorBackground
is defined as navy in Theme.Monzo.Dark
but it is off-white in Theme.Monzo.Light
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <style name="Theme.Monzo.Light" parent="Base.Theme.Monzo"> <item name="android:colorBackground">@color/off_white</item> <item name="colorOnBackground">@color/navy</item> <item name="colorSurface">@color/white</item> <item name="colorOnSurface">@color/navy</item> <item name="colorPrimary">@color/off_white</item> <item name="colorOnPrimary">@color/navy</item> <!-- ... --> </style> <style name="Theme.Monzo.Dark" parent="Base.Theme.Monzo"> <item name="android:colorBackground">@color/navy</item> <item name="colorOnBackground">@color/white</item> <item name="colorSurface">@color/dark_grey</item> <item name="colorOnSurface">@color/white</item> <item name="colorPrimary">@color/navy</item> <item name="colorOnPrimary">@color/white</item> <!-- ... --> </style> |
When all views and layouts only reference color properties from our themes, adding the “night-mode” mode to the application becomes trivial: we can override the application themes. Use this in the values-night
resource directory with a different set of color values.
Base theme
Our base theme, Base.Theme.Monzo
, is where we override or define default styles for views and text appearance properties.
This layer usually does not contain references to specific colors. Instead, the style resource used here will reference properties from the application themes.
1 2 3 4 5 6 7 8 | <style name="Base.Theme.Monzo" parent="Platform.Theme.Monzo"> <!-- ... --> <item name="tabStyle">@style/Widget.Monzo.TabLayoutLegacy</item> <item name="textInputStyle">@style/Widget.Monzo.TextInputLayout</item> <item name="toolbarStyle">@style/Widget.Monzo.Toolbar</item> <!-- ... --> </style> |
Avoid using hardcoded colors in this layer, meaning everything common to all themes is possible here.
Platform theme
The platform theme layer allows us to calculate API specific properties. Using resource qualifiers, we can specify a different platform theme for different Android versions.
1 2 3 4 5 6 7 8 9 10 11 | <?xml version="1.0" encoding="utf-8"?> <resources> <style name="Platform.V23.Theme.Monzo" parent="Platform.V21.Theme.Monzo"> <!-- Attributes which are only available from API 23 --> </style> <style name="Platform.Theme.Monzo" parent="Platform.V23.Theme.Monzo" /> </resources> |
1 2 3 4 5 6 7 8 9 10 11 | <?xml version="1.0" encoding="utf-8"?> <resources> <style name="Platform.V21.Theme.Monzo" parent="Theme.MaterialComponents.Light.NoActionBar" /> <style name="Platform.Theme.Monzo" parent="Platform.V21.Theme.Monzo" /> <!-- ... --> </resources> |
This works based on the following principles:
- Base theme depends on
Platform.Theme.Monzo
Platform.Theme.Monzo
is defined in each resource group where we need version-specific attributes- Each version of Platform.Theme.Monzo depends on the theme resource specific to the version, e.g.
Platform.V23.Theme.Monzo
- Version-specific topic resources inherit from resources reserved for older versions, unless it starts minSdkVersion in that case, it will depend on the theme framework.
Framework theme
We have chosen Theme.MaterialComponents.Light.NoActionBar as the theme framework to inherit. The framework theme provides many reasonable default values so that we cannot specify everything.
Renaming and pruning trees
With so many themes and styles, it’s hard to know where to start. Part of this difficulty is that it is unclear how to use the themes because it is not clear what style resources are the theme. We decided to apply a strict naming convention to help us navigate the current state of the application.
The first rule we agree is to take advantage of the dot notation if possible, preserving parent use explicitly only in two cases:
- theme overlay declaration, where we don’t want to inherit any properties
- Change the namespace when inheriting from a style from another family
This makes it really easy to see the origin of a topic by eliminating the indirect:
Ref: https://medium.com/monzo-bank/refactoring-android-themes-with-style-restructuring-themes-15230569e50