Implementing Dark Mode on Android — part one

Lessons learned from a single Activity, multi-flavored, dynamically themed Application

Photo by Kelly Sikkema on Unsplash

The latest hype for our consumers seems to be the dark mode that every application should support, be it on a phone, on the web or desktop. We already feel a bit disappointed if faced with an app that only has a light mode.
Even though Android 10 and iOS 13, where dark mode first came as a part of the system, only is a couple of months old.

At the moment I’m consulting at Bonnier News Tech with four of their Android apps. I had the opportunity to implement dark mode on them during the summer and I like to share my thoughts and some of my problems as I think that if that app can implement dark mode, all else can too.

My Baseline

The main newspaper app, Expressen, includes articles from all four brands (Expressen, GT, Kvp, Sport) and each brand has its theme. When entering an article from a specific brand, the entire app should use that theme. There’s also a section that isn’t a separate brand, but a section with a separate theme. Each brand also has its app, so four flavors. Thankfully they all default to a light background and dark text for the content. But the headers differ.. some are light and some are dark.

The app is a single Activity/multiple Fragments app and we support back to API version 21. And native dark mode support is from API version 29.

So there was my baseline for implementing dark mode. Let’s dig into the code now to see how I went about it.

Colors; got to get them all!

I had the pleasure of being given a specification of all the colors that should exist in the app. So I knew that if any color wasn’t in the specification it should be removed. They also had a standardized naming on them c1, c2 and so on.

Since I wasn’t a part of creating the app. I didn’t know exactly how the colors and theming was implemented. So a good alternative is to throw them off completely. I had a resource file named palette.xml where all colors where specified. It contained colors from light_grey to dark_grey and background. They where assigned brand new colors such as pink, purple, orange. The app looked amazing… Then I added my color specification as c1, c2 and so on with the correct colors. And the goal was to only have those colors left in the end.

As a side note, here I found that the app had a lot of dynamic remote configurations of colors that needed to transform into pre-defined in-app colors and themes. But that’s another story. But thanks to the above approach I saw it directly instead of in the end.

Make your attributes

Next up was to have the proper attributes for colors, to be able to utilize themes in android properly we should use style attributes. There are a few we often use that is built into android and the full list is available here.

I chose to define most of the attributes myself for a clear view. Except for text colors and colorPrimary and colorAccent which most developers are very used to by now. But I added themeBorder, themeOverlayBackground, themeContentBackground and a few more to cover the app-specific layout sections that needed theming.

The theme

Then in my theme.xml I declared a base theme for the main app, this theme should extend Theme.MaterialComponents.DayNight. In my base theme, I set all the style attributes to the correct colors from my palette. But I chose to complicate (or separate) it all one more step. Since I don’t want to use the c1, c2 colors explicitly since they are specified by our UX, and might be subject to change. So I added new colors and prefixed their names with mode_. This way I can have one palette file for dark mode and one for light mode specifying the color mode_content_background to the correct color from the specification for that mode.

Let’s look at the content background color full chain to clarify

// file: values/palette_spec.xml 
// Here I specify all colors allowed
<color name="c1">#212121</color> // almost black
<color name="c5">#FAFAFA</color> // almost white
// file: values/palette.xml
// And the light mode specific palette
<color name="mode_color_background">@color/c5</color>
// file: values-night/palette.xml
// And the light mode specific palette
<color name="mode_color_background">@color/c1</color>
// file: values/attrs.xml
// The styled attribute to be used by the views
<attr name="themeContentBackground" format="color" />
// file: values/theme.xml
// And in our theme that connect our attributes with colors
<item name="themeContentBackground">
@color/mode_content_background
</item>

One thing that I haven’t mentioned yet is the night qualifier that I have for the dark mode version of the palette. It has been in android since API version 8 so it’s mostly safe to use even though the dark mode is from API version 29. (There are some risks that we will discuss later).

Make our views depend on the theme

Now that we have the setup, we need to do some implementation of it as well. My app is still pink and purple and screaming. So we need to change all hardcoded colors into using styled attributes instead. So for every #xxxxxx and @color we have to change it to the corresponding ?styledAttribute. And I really mean all of them! They lurk in the darkest dungeons, like a <selector> or a <vector> shape. By doing this we can remove our old colors one by one. Or if you don’t care about visual feedback you can also just remove all your old colors from the top and just replace their references with the help of compiler errors.

Done?

And that’s actually my preferred setup. Simple as that, we should be done! All views use styled attributes, our themes define what colors they should be, our colors exist in both dark and light mode, and the theme is specified by the manifest. This is how I would build dark mode from scratch.

But let’s embrace Murphys’s law and address the problems as well. I did say that I dealt with dynamic themes, several flavors, etc. In the second part of this article, I’ll walk you through my specific problems and how I solved them. Hopefully, it’ll prevent some of you from bumping into the same issues.

Update: Read the second part of the article here.

Thank you for your time!
→ Bob

Lead Developer at Qvik, Coach, Agile Thinker, GDG Lead.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store