Undoubtedly one of Android Q’s (aka Android 10 or API 29) most notorious features is Dark Theme. It gives users the option to switch their apps across the whole system to a dark theme. This comes with a few benefits for the user including:
- Reduced power usage by a significant amount (depending on the device’s screen technology)
- Improved visibility for users with poor vision and high sensitivity to bright screens
- Easier to use in a low-light environment
(Advantages taken from https://developer.android.com/guide/topics/ui/look-and-feel/darktheme)
Right now, pretty much Google’s whole suite of apps as well as well known big names in the app industry such as Facebook already support Dark Theme.
Unlike iOS though, we Android developers have to manually opt-in to make our apps support Dark Theme. The good thing is, doing this is dead simple.
Adding Dark Theme to your app
The way to enable Dark Theme support in your app is to simply set the DayNight theme in your styles.xml.
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
(You can still extend this to declare NoActionBar, DarkActionBar, etc. like you could with other themes)
This overrides the system-default colours in your app to show a light theme while the device isn’t using Dark Theme, and a dark theme when it is.
This however doesn’t override colours in your app that you’ve set yourself. E.g. you manually set a white background in one of your activities.
There’s a proper way around this… and there’s a quick way around this. Let’s go over the quick way first.
Force Dark
I get it. We don’t always have the time or budget to go over each activity in our app and do a proper implementation of Dark Theme.
<style name="AppTheme" parent="Theme.AppCompat.Light"> ... <item name="android:forceDarkAllowed">true</item> </style>
This attribute analyses your views and decides if their colours need to be ‘darkifed’ or not, assuming those colours were meant to be applied for a light theme. Thus, this attribute requires your theme to be Light and not DayNight.
When you do this however, you have to thoroughly test your app to see if everything looks the way it should be. Some views may not look right the way the system decides to darkify them.
To fix this, you can individually opt views out of forceDark with one simple attribute.
android:forceDarkAllowed="false"
This leaves your view unaffected by forceDark.
This solution isn’t very scaleable though. Every new UI you make will require extra testing just to see if forceDark works well with that view. Thus, there’s a more proper solution to implementing dark theme in your app.
Dark Theme the Proper Way
After setting the DayNight theme, the first step in properly implementing Dark Theme is to make sure you follow some material guidelines and avoid some pitfalls. The most prominent one being:
Avoid hardcoding text and background colours
If you’re using standard black and white colours for your text and background, there should be no reason to avoid using the material-design recommended colours provided by the SDK. As listed in the Developer Docs, these include:
- ?android:attr/textColorPrimary – This is a general purpose text color. It is near-black in Light theme and near-white on Dark themes. It contains a disabled state.
- ?attr/colorControlNormal – A general-purpose icon color. It contains a disabled state.
- android:attr/colorBackground – A general purpose background colour
And of course, there’s also a wide variety of theme colour attributes you can make use of.
More Customised Dark Theme Colour Switching
So maybe your brand requires some specific shades of black or white, or you’re using colours that aren’t even in the greyscale. It’s time to make use of configuration changes.
Configuration Changes
You can handle the implementation of Dark Theme in your code by declaring the uiMode configuration change in each of your activities.
<activity android:name=".MyActivity" android:configChanges="uiMode" />
This makes it so that whenever the app detects a theme change, it will call the onConfigurationChanged() method. Then you can run code like this to detect the theme and make the changes you want.
val currentNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK when (currentNightMode) { Configuration.UI_MODE_NIGHT_NO -> {} // Night mode is not active, we're using the light theme Configuration.UI_MODE_NIGHT_YES -> {} // Night mode is active, we're using dark theme }