In the first part of this post, we have created a light theme and made initial preparation to support multiple themes. In this blog post, we will continue that effort, creating another theme and allowing dynamic switching of themes during runtime.
Ideally, if we treat theme as a configuration, we should be able to specify theme-specific resources under a ‘theme-qualifier’ resources directory, e.g.
values-dark for dark theme resources and
values-light for light theme resources. Unfortunately, this is not yet an option at the time of this post.
So how should we specify resources for multiple themes? If we look at how resources are organized in
appcompat, we will have a rough idea of how the Android team organize their theme specific resources. Materialistic also employs a similar approach.
Here we add a new dark theme called
AppTheme.Dark, and for style and color consistency, we extend from
Theme.AppCompat (a dark theme). Unfortunately, since our two themes extend two different base themes, we cannot share any common attributes (the same way a class in Java cannot extend two or more classes).
The two themes should have appropriate (different if applicable) values for base Android and
appcompat theme attributes, e.g.
android:textColorPrimary for dark theme should be light, and for light theme should be dark. By convention, here we suffix alternative theme colors with
TipTry out your alternative theme by temporary switching
AndroidManifest.xmlto see what extra colors/style you need to create. For certain cases a color may look okay in both dark and light theme.
At this point, we should have a pretty decent dark theme for our app, except for some anomalies here and there, e.g. drawables used for action bar menu items. A dark action bar expects light-color menu items, and vice versa. In order to tell Android to use different drawables for different app themes, we create custom attributes1 that allow specifying reference to the correct drawable, and provide different drawable references as values for these custom attributes under different themes (the same way
appcompat library provides custom attributes such as
Similar implementation can be used to specify most custom attributes you need for theme specific resource values. One hiccup to this approach is that attribute resolving in drawable resources seems to be broken before API 21. For example, if you have a drawable which is a
layer-list of colors, their values must be fixed for API <21. See this commit from Google I/O 2014 app for a fix.
An alternative approach to avoid duplicating drawable resources for different themes is to use drawable
tint. This attribute is available from API 21. Dan Lew in his blog2 shows how to do this for all API levels. Personally I would prefer to keep my Java implementation free of view logic if possible, so I choose to have different drawable resources per theme.
Dynamic theme switching
Now that we have two polished themes ready to be used, we need to allow users to choose which one they prefer and switch theme dynamically during runtime. This can be done by having a
pref_dark_theme to store theme preference and use its value to decide which theme to apply. Application of theme should be done for all activies, before their views are created, so
onCreate() is our only option to put the logic.
Here, since our app already has a default light theme, we only need to check if default preference has been overriden to override dark theme. The logic is put in the ‘base’ activity so it can be shared by all activities.
Note that this approach will only apply theme for activities that are not in the back stack3. For those that are already in current stack, they will still exhibit previous theme, as going back will only trigger
onResume(). Depends on product requirements, the implementation to handle these ‘stale’ screens can be as simple as clearing the back stack, or restarting every single activity in the back stack upon preference change. Here we simply clear back stack and restart current activity upon theme change.
So that’s it. Now we have an app with two polished themes for even the most picky users! Head over to hidroh/materialistic GitHub repository to checkout complete implementation!