Android 10 — How to officially support Dark Mode in your apps?
Everyone out there are talking about this trending topic these days when android 10 and iOS 13 has just been released. There are many apps have already had Dark theme options before that. But now it comes to be standard feature of the system, not only the app itself anymore.
The questions immediately appear for us now are “what is dark mode”, “is it mandatory to support dark mode in our apps”, “what do we need to do and how long it take to adapt that changes”, “which people in the team need to be involved”…
In our company, we held the whole day with all android developers just to make that preparation prior to supporting dark mode in our apps, we tried to figure out as clear as possible those concerns.
Dark mode is the trend, users love it
Apart from dark colors which are favorite style of many people, it provides exciting battery optimization and eye-friendly visuals, according this.
From my point of view, as a user, I would be excited if the app I’m using delivers an update with the change log includes a line “dark mode supports”. Wow! they’re so professional with impressive adaptability. They would get more loyal users, possibly.
But there shouldn’t be a mistake if your app doesn’t support dark theme at the moment or in near future, just a little bit inferior for professional companies :D.
Dark theme is not required. In device setting there are no way for users to force your app into the dark without your allowance done by some configurations in the project codes. But perhaps the user is using night mode, for example in low light condition, then he gets a push message and opens your app: wtf, too bright suddenly, my eyes would be hurt!!!
The way to go
It’s not only your responsibility to bring dark theme into the project but the whole team, especially UI/UX designers. Both developers and designers should have common understanding prior to start implementing.
The developer documentation is pretty clear in android developer site, therefore I’m not going to rewrite them here but showing a big picture to analyze the needs for development team to be ready for it.
Force dark — the easiest option but risky
From above documentation you can find there is an option allowing the system to auto converts your theme into dark theme:
Force Dark analyzes each view of your light-themed app, and applies a dark theme automatically before it is drawn to the screen
But it’s really risky since the colors might not be set correctly for all elements, as well as it doesn’t know what are exactly your UX’s desires :D.
Another option is mixing manually supports for some screens but force dark for the rest to speedup progress but they would stay inconsistently. Therefore it’s not the case for really serious products.
Following are steps we need to do:
#1st step: redefine color system
Theming is mostly based on colors — the first thing we need to pay much attention to.
Perhaps we have some colors:
<resources>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="yellow">#FFFF00</color>
<color name="fuchsia">#FF00FF</color>
<color name="red">#FF0000</color>
<color name="silver">#C0C0C0</color>
...
</resources>
And many of us are doing like this, using hard-coded color in styles and layout files:
<style name="TextBody" parent="TextAppearance.AppCompat.Body2">
<item name="android:textSize">@dimen/text_small</item>
<item name="android:textColor">@color/black</item>
</style>
Imagine that if dark mode is turn on but your texts are still black as hard-coded color is set in above style. It maybe the same situation for UI/UX designers when they define element style guide in Sketch/Zeplin. It’s very hard to support multiple themes this way.
It’s ok to override red
, yellow
or whatever color in dark mode from default light values, but red
would no longer be red
to be visible with high enough contrast, thus this is not the most right way to override literal color values.
Use color attributes instead
As each UI element’s colors will be different depends on current theme, so the correct approach is using attributes.
The material design site defines clearly the color theming system based on attributes such as colorPrimary
, colorSecondary
, colorBackground
, colorError,
colorOnError
, textColorPrimary
, textColorSecondary
…
Back to above example, the textColor
should be set with textColorPrimary
attribute instead of always black. This attribute will be override in specific theme base on user selection:
<style name="TextBody" parent="TextAppearance.AppCompat.Body2">
<item name="android:textSize">@dimen/text_small</item>
<item name="android:textColor">?attr/textColorPrimary</item>
</style>
If you are using Theme.AppCompat.DayNight
these attributes are already implemented for both dark and light theme with its default colors. We are still be able to override these colors which will be described in next section.
If the default attributes are not enough, or at least their names are not clear enough for us to understand and match our style guide, we need to add custom color attributes.
Defining how many colors needed is difficult part. We should follow project style guide (if existed) and collaborate closely with UI/UX designers to adapt new color system for current designs.
#2nd step: link color attributes to styles and layouts
After having new color system, lets go through all styles and remove hard-coded colors by those attributes such as above example.
Another point is that we may have layout files with ui components are not styled but use screen-specific colors, or even the colors are set programmatically in the code. All of them need to be replaced as well. Then you would find more and more cases need to be discussed with UX. Everything should be standardized in this step no matter how bad it was.
#3rd step: check all icons and illustrations
Icons and illustrations are also super important for the app’s appearance. Basically for each theme we have to show the icon with different color accordingly.
For single color icons (same color for all visible pixels), it’s possible for us to need only 1 icon file each and apply color attributes by using vector icon or tint
mode.
For multi-color icons, such as illustrations, we should have 2 versions (Dark, Light) for each, and place each one in the correct specific configuration of corresponding theme (described in next section).
Again, check all of them if anyone is using hard-coded colors and replace with attributes.
#4 step: manual testing over places
Eventually we need dedicated people to verify that every single screen, single element plays well on both Dark and Light mode.
Please also check the notification icons, messages and widgets as well as launcher screen, if has.
Finally ship the app with impressive release notes :D
Don’t forget to update store listing screenshots.
Implementation tips
Defining colors is work of UI/UX designers and we are developers being responsible for implementing them into project.
Default themes
Theme.AppCompat.DayNight
or Theme.MaterialComponents.DayNight
are default themes which already implemented so many attributes on both Day or Night mode. Implementing either of them is the easiest way to support Dark Theme in your app.
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
But in the fact that most of apps has their own style guide with different colors than default values implemented by those. So you have to do customizations more or less for sure. Please note that using those themes is optional, it’s still possible to have Night mode of your own style without inheriting those, just to make sure necessary attribute implementation for each mode, based on following instructions.
New resource configuration
You may already known that Android SDK supports us with multiple resource configurations such as different screen sizes (ex. values-xxhpdi)
, api levels (ex. values-21
) or languages, etc… We can override resources in those folders for different device configurations.
And values-night
is the new configuration name for resources under Night theme (dark) applied for all attributes, colors, drawables… Simply create a folder named values-night
in resource directory of the module then put necessary values there.
Override theme attributes
In case of you want to use your own colors, instead of default one in the theme, just override desire attributes:
styles.xml
in default resource directory values
:
<style name="AppTheme" parent="Base.AppTheme">
<item name="colorPrimary">@color/white</item>
</style>
<style name="BaseAppTheme" parent="Theme.AppCompat.DayNight">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
styles.xml
in values-night
:
<style name="AppTheme" parent="Base.AppTheme">
<item name="colorPrimary">@color/black</item>
</style>
Then ?attr/colorPrimary
on default theme (Light) is white
, switching to Night theme it is black
.
Colors
Perhaps we have some text colors: textColorTitle
and textColorDescription
need to be different on Dark and Light mode. There are 2 approaches:
#approach 1: defining colors in colors.xml
in default resource folder (Light theme)
<resources>
...
<color name="textColorTitle">#000000</color>
<color name="textColorDescription">#0000FF</color>
...
</resources>
and override colors.xml
in values-night
folder for Dark theme:
<resources>
<color name="textColorTitle">#FFFFFF</color>
<color name="textColorDescription">#FFFF00</color>
</resources>
And use it:
<style name="TextBody" parent="TextAppearance.AppCompat.Body2">
<item name="android:textSize">@dimen/text_small</item>
<item name="android:textColor">color/textColorTitle</item>
</style>
#approach 2: use theme attributes (recommended)
Defines custom attributes in attrs.xml
:
<resources>
<attr name="textColorTitle" format="color" />
<attr name="textColorDescription" format="color" />
</resources>
Set attributes colors for default AppTheme
in styles.xml
:
<style name="AppTheme" parent="BaseAppTheme">
<item name="textColorTitle">@color/black</item>
<item name="textColorDescription">@color/gray</item>
</style>
Create styles.xml
file in folder values-night
and override attributes colors for the AppTheme
:
<style name="AppTheme" parent="BaseAppTheme">
<item name="textColorTitle">@color/white</item>
<item name="textColorDescription">@color/silver</item>
</style>
Then use attribute reference:
<style name="TextBody" parent="TextAppearance.AppCompat.Body2">
<item name="android:textSize">@dimen/text_small</item>
<item name="android:textColor">?textColorTitle</item>
</style>
Obviously this approach is much cleaner than the approach#1. Everyone looking at the theme items would know exactly how the attributes are set for each theme. We also don’t need to override colors which is pretty confuse, but we keep single colors.xml
just holding all literal values black, white, red, green, gray…
Custom attributes is the right way to deal with multiple themes, not only for just Dark and Light, such as a music player app with 5 different material themes to satisfy as many as users, we can create 5 themes and override attribute values on each one.
Remember that
values-night
resource configuration is just a support from android SDK for the dark mode switching in android system. In the past (below Android 10) without it we were still able to create Dark Theme using custom attributes.
There maybe some places that you set colors programmatically in code instead of declaratively in xml files, by whatever reason, you would need to resolve theme attributes following this.
Icons and illustrations
For vector icons, it’s pretty easy to change color
attribute in its xml file:
android:fillColor="?iconColor"
But for better usage in different situations, vector icons with single color are recommended to be set with black
as always and use tint
attribute at specific place, see this.
For png icons, it’s impossible to change the color dynamically excepting using tint
.
For illustrations with multiple colors, tint
would not working, changing color for certain parts of the vector icon is crazy. Example this:
The solution would be design another one for Dark theme with suitable colors, then place that one in drawable-night
:
Conclusions
Being early adopter for new technologies demonstrates your development team is professional as well as presenting the user-centric direction of the product.
Supporting dark mode is definitely a good chance for you to refine color system and standardize style guide, be ready for any further changes.
Thanks for reading and looking forwards your responses 👌👍