Theme attribute all the things
In the previous posts in this series on Android styling, we looked at the difference between themes and styles and why it’s a good idea to factor out things that you wish to vary by theme and common theme attributes to use:
This enables us to create fewer layouts or styles, isolating changes within a theme. In practice, you largely want to vary colors by theme and as such you should always* refer to colors via theme attributes.
Always* refer to colors via theme attributes
That means you can consider code like this to be a smell:
Instead you should refer to a theme attribute, allowing you to vary the color by theme, for example, providing a different value in dark theme:
Even if you’re not currently supporting alternate themes (what–no dark theme??), I’d recommend following this approach as it’ll make adopting theming much easier.
You can vary colors by providing alternate values in different configurations (e.g.
@color/foo defined in both
res/values/colors.xml and an alternate value set in
res/values-night/colors.xml) but I’d recommend using theme attributes instead.
Varying at the color layer forces you to give semantic names to colors i.e. you likely wouldn’t name a color
@color/white and provide a dark variant in the
-night configuration — that would be pretty confusing. Instead you might be tempted to use a semantic name, like
@color/background. The problem with this is that it combines both the declaration of the color and providing the value. As such it gives no indication that this can or will vary by theme.
@colors can also encourage you to create more colors. If a different situation calls for a new semantically named color with the same value (i.e. not a background but should be the same color), then you still need to create a new entry in your colors file.
By using a theme attribute we separate the declaration of semantic colors from providing their values and make call-sites clearer that the color will vary by theme (as they use the
?attr/ syntax). Keeping your color declarations to literally named values encourages you to define a palette of colors used by your app and vary them at the theme level, keeping your colors file small and maintainable.
Define a palette of colors used by your app and vary them at the theme level
The added benefit of this approach is that layouts/styles referring to these colors become more reusable. Because themes can be overlaid or varied, the indirection means you don’t need to create alternate layouts or styles just to vary some colors — you can use the same layouts with a different theme.
I placed an asterix on “always* refer to colors via theme attributes” because there may be occasions where you explicitly don’t want to vary a color by theme. For example, the Material Design guidelines call out occasions where you may wish to use the same brand color in both light and dark themes.
In these rare cases, it’s perfectly valid to refer directly to a color resource:
This might be valid if
primary_20 is a
ColorStateList which itself refers to theme attributes for the color values (see below). While commonly used to provide different colors in different states (pressed, disabled etc)
ColorStateLists have another capability that can be useful for theming. They let you specify an alpha value to be applied to a color:
This kind of single-item-
ColorStateList (i.e. only supplying a single, default color, not different colors per state) helps reduce the number of color resources that you need to maintain. That is rather than defining a new color resource that manually sets an alpha value on your primary color (per configuration!) instead this alters whatever
colorPrimary is in the current theme. If your primary color changes you only need to update it in a single place, not hunting down all instances of where it has been tweaked.
While useful, there are some caveats to this technique to be aware of.
1. If the specified color also has an alpha value, then the alphas are combined e.g. applying 50% alpha to 50% opaque white would yield 25% white:
For this reason, it’s preferable to specify theme colors as fully opaque and use
ColorStateLists to modify their alphas.
2. The alpha component was only added in API 23 so if your min sdk is lower than this, be sure to use
AppCompatResources.getColorStateList which backports this behavior (and always use the
android:alpha namespace, never the
3. Often we use a shorthand to set a color as a drawable e.g.
View’s background is a drawable, this shorthand coerces the given color to a
ColorDrawable. However there is no way to convert a
ColorStateList to a
Drawable (before API 29 when
ColorStateListDrawable was introduced to solve this issue). We can however work around this restriction:
Be sure that your background tint supports the states your view needs e.g. if it needs to change when disabled.
So you’re convinced that you should be using theme attributes and
ColorStateLists, but how do you enforce this across your codebase or team? You can try to be vigilant during code reviews but this doesn’t scale well. A better approach is to rely on tooling to catch this. This article outlines how to add a lint check to look for literal color uses and could be extended to cover all advice in this article:
Using theme attributes and
ColorStateLists to factor colors out to your theme makes your layouts and styles more flexible, promoting reuse and keeping your codebase lean and maintainable.
Join us in the next post where we’ll look more at using themes and how they interact: