Implementing Dark Mode on Android

April 18, 2020

ev.jpg

Here are a few steps needed to add Dark Theme support to your Android app. This is not a comprehensive guide and your milage may vary. Especially, as the APIs evolve.

TL;DR:

Dark theme applies to both the Android system UI and apps running on the device.

To implement support for Dark Mode, you’ll be dealing with AppCompatDelegate. Be aware that it’s done differently on Android 10 (API level 29) and higher vs previous versions.

  • Android 10 and higher -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM

  • Pre-Android 10 -> AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY

Adding Dark Mode support typically involves dealing with AppTheme and keeping track of the current setting in SharedPreferences. For details, read on:

Force Dark Mode

If you are only supporting Android 10, you may be able to force dark mode without having to deal with the above settings by simply opting-in in the Dark Mode in styles.xml:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="android:forceDarkAllowed">true</item>

And then overwriting the setting in widgets, if needed:

<Button 
  android:forceDarkAllowed="false"

Besides the fact that it only works on Android 10, you will likely run into limitations down the road. So let’s look into a more comprehensive approach of implementing Dark Mode on API levels 14+.

First, revert the force dark mode change made above.

Material Gradle dependency

Material dependency is needed for day night theme material support:

implementation "com.google.android.material:material:$materialVersion"

AppTheme with Dark Mode support

Update your styles.xml to use one of the Material Day Night themes:

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">

Style Day and Night resources differently

For widgets that should be presented differently in night mode, update res/values-night/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="MyImage" parent="AppTheme">
        <item name="android:src">@drawable/my_night_image"

Persist Night Mode setting

For supporting Android 10 and above, we need to map user input to one of the following:

  • AppCompatDelegate.MODE_NIGHT_NO

  • AppCompatDelegate.MODE_NIGHT_YES

  • AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM

Note, for API levels between 14 and 28, AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY is equivalent to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,

Typically, you’d have a menu to let user make a choice. For instance:

Screen Shot 2020-04-18 at 11.04.38 AM.png

Once you get user input, perform the following steps:

  1. To restart all started Activities, call AppCompatDelegate.setDefaultNightMode(dayNightMode)

  2. Persist user selection in SharedPreferences

Icons to handle Day Night Mode switching

Let any vector assets automatically adjust color based on android:tint setting:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
   android:tint="?android:colorControlNormal"

Retain Dark Mode settings between app restarts

Every time the app starts, you need to read the SharedPreferences to honor Dark Theme user setting. You can do that in the Application class:

class MyApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    val dayNightMode = // read from SharedPreferences
    AppCompatDelegate.setDefaultNightMode(dayNightMode)
  }	  

Manually handle config changes with uiMode

When the app’s theme changes, it triggers a uiMode configuration change. This means that Activities will be automatically recreated.

If you don’t want to immediately respond to this change (for instance, when a video in your app is playing), you need to manually handle uiMode in AndroidManifest.xml for a specific Activity:

<activity
    android:name=".MyActivity"
    android:configChanges="uiMode" />

And override onConfigurationChanged() in the Activity itself:

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    val currentSystemMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK

    when (currentNightMode) {
      Configuration.UI_MODE_NIGHT_NO -> // Night mode is not active 
      Configuration.UI_MODE_NIGHT_YES -> // Night mode is active
    }
}

Check out Dark Theme for more details on supporting Dark Theme.

 
Previous
Previous

Writing a custom Moshi Adapter

Next
Next

Making Android app Material