The 1–2–3s of bringing dark theme to your Android app

Ajay Raghav Gurubabu
VMware 360
Published in
7 min readApr 9, 2020

Android officially added a system level toggle for dark theme in Android 10, sparking a race to get apps into dark theme. To provide users with the app experience they prefer, VMware added dark theme to VMware Boxer on Android. The following are the 1–2–3s of bringing dark theme to your Android applications based on our experiences.

Android documentation for dark theme can be found at

Goals and wins

When we started off with dark theme, we were the first app team in our organization to do so. We wanted to apply these learnings to the other apps that followed. VMware Boxer has a huge codebase with a mix of legacy code and new code. So, VMware Boxer naturally became a great candidate to lead our entire suite of apps to jump over the dark theme fence. We aimed to achieve the following:

  • Cleaner UI code — Android’s UI is split across different XMLs like the layouts, styles, attributes, colors, drawables etc. Using them efficiently, like using attributes for textColors instead of using colors as is, cleans up UI code.
  • Cleaner Light mode — We used this dark mode effort as an opportunity to fine-tune our light mode UX, to polish the rough edges and reach a more consistent UX across both light and dark themes.
  • Future-proofing — Obviously, it would be a bad UX if a new screen tomorrow is not in dark mode or if it breaks any existing screens. We needed a way to catch the potential defects in development. While this is a work in progress, we have made some solid progress here.
  • Dark mode UX — This is the ultimate end goal. How we reached the end goal is what’s to follow, while touching on the above expectations.

Vision

Vision is our mobile UI framework library, which we use to drive consistency, reusability, VMware branding and platform standards to our suite of apps. We wanted to have a broader vision for our app development to reduce and reuse, not repeat engineering efforts across different teams and more importantly to have a consistent UI experience. Naturally, Vision became the center for the efforts related to dark theme. Vision laid the groundwork and foundations for VMware Boxer and the other apps and libraries that needed dark theme. Vision provided the colors, styles and some of the drawables needed for apps to enable dark theme. We adopted the Google helpers on dark theme from the links shared above and we tried to make it easy for our apps to consume the Vision library.

How did we go about this effort ?

Step 1 A — Refactor to semantic colors.

I understand there could be many :facepalm: when the first step has to be a refactor. But hey, we have to do what we have to do. During the lifetime of an application and its numerous designers and developers, an application could accumulate many shades of a single color. Each new design change could eventually result in the creation of a new color which would be difficult to manage. If this does not apply to you, 👏 👏. This step involves crunching those colors into a smaller set of colors that we call ‘Semantic colors’ which are meant to be used everywhere in apps. Semantic colors are colors named based on a use case and are meant to be reused as in the example below.

Use these styles in the layouts directly instead.

Step 1 B — Convert PNGs to SVGs

Some of the main problems with PNGs are that they are bulky, hard to scale and difficult to maintain multiple versions of a single drawable across different densities. For example, a PNG like ic_overflow.png could have versions in hdpi, mdpi, xhdpi, xxhdpi at least. To make the overflow drawable dark theme compatible, adding just a drawable-night folder won’t help as that would make it density independent and harder to scale. We might end up adding hdpi-night, mdpi-night etc. which doubles the PNG count. Converting them to SVGs gives us a great head start in refactoring and moving to dark mode. SVGs scale well without multiple density-based versions and we can change the colors of the icons with ease between light and dark themes. Yes, we can use `android:tint` on PNGs but it is not a fool-proof way. What if the icons are not single toned?

A sample vector from the SVG for back arrow is

Notice that there is android:fillColor=”#ffffff” and this is device/spec/small width independent.

Step 2 — Use Attributes

Using semantic colors directly is a preferred method over using hardcoded colors or creating multiple shades or copies of colors. But one could assume that in a good majority of an app’s screens, the primary colors like the text, backgrounds, etc are going to be the same. If that is the case, we can reach the best efficiency by using the attributes directly in our layouts. Android has a lot of attributes already defined in their Styles which are overlooked in many cases. With VMware Boxer, we wanted to use them to achieve a much better coding process for UI to keep the code clean. Simple attributes include `textColorPrimary`, `textColorSecondary`, `windowBackground` etc.

Best case example with attrs

The above styles unleash a lot of potential as explained below:

  • Case 1 — There are two screens with the same color pattern for the header and sub header — in this case, just use the styles as is.
  • Case 2 — There are two screens with different color patterns for the header and sub header — in this case, the layout is still the same when using styles. Just setting the primary and secondary text colors correspondingly for the two parent styles would do the trick.

Following the above style-based approach keeps developers from creating multiple color definitions, styles, layouts, or any other necessary resources.

To unlock more potential, we created custom attributes based on use cases which are expected to be consistent across different screens. For example, all our primary icons like the settings gear, overflow menu, edit pencil, etc. are going to be the same color in all of our apps. What would be the best way to change simple icon colors between light and dark themes as well as handling a case where the icon shades change in the future such as from black to grey? To solve this problem, we created a custom attribute `?iconColor` in our Vision library and set it to the semantic color, `color/iconPrimary`. Now, the same vector from step 1A becomes

Here, when `?iconPrimary` is set to say `red` for an app branded to be red primary, the parent style just needs to set `?iconPrimary = color/red` in their base style and voila

VMware Boxer screens in light and dark theme

Step 3 — Use DayNight style

In order for apps to seamlessly transition from light to dark and vice versa, the application needs to piggyback on the system level calls to make the switch between the modes. That is automatically handled by Android’s theme `Theme.MaterialComponents.DayNight’ as mentioned in Android guidelines. The two steps above have all the field work needed. The only other thing pending are the styles. Vision provides a theme for VMware Boxer and other apps to follow and to be the parent for its main theme. Vision now marries the semantic colors and the attributes to provide the base style which is constantly managed to add more attributes and use cases as needed. A snippet from our styles is below

Each of these semantic colors have a light and night definition. For example, color/textPrimary has black and white in light and night respectively. color/backgroundWindow has slate8(#F2F2F2) and coal1(#121212) in light and night respectively

Now when VMware Boxer and other apps extend their base theme from `VisionTheme.DayNight` their apps will start responding to dark mode and light mode :)

App responding to device wide dark theme toggle

Conclusion

In this post, we talked about how, with the help of a common UI focused library and some rewiring, we were able to achieve dark mode for a pretty complicated app, VMware Boxer with ease. Often, an app has multiple modules which are imported as libraries and they tend to have screens that needs to be transformed too. The learnings from VMware Boxer were carried over to other libraries and vice versa with Vision as the main engine. This helped us get every piece of VMware Boxer into dark theme with the above steps. We took these learnings to newer apps and we are developing them with dark mode support from scratch.

“ Beneath this mask there is an idea, Mr. Creedy, and ideas are bulletproof “ — V

This dark mode effort not only got us to our end goal of adding dark mode to VMware Boxer, but along the way we cleaned up the code for better UI development and made our light mode more consistent than before.

Part 2 will continue on

  • Learnings and best practices
  • Color definitions in light and night mode
  • Testing efforts
  • Future proofing

--

--