DayNight — Adding a dark theme to your app

DayNight — Adding a dark theme to your app

This post has been updated multiple time since first publishing. The content is correct as-of March 13th 2019.

The DayNight functionality in AppCompat allows your app to easily switch between a dark ⚫ and light ⚪ theme. This has many benefits for your users, especially if you have a content-heavy app (such as a Reddit client).

How do I use it?

You need to change your theme to extend from one of the DayNight variants, and then call one method to enable the feature. Here’s an example theme declaration:

<style name="MyTheme" parent="Theme.AppCompat.DayNight">
    <!-- Blah blah -->
</style>

If you’re using Material Design Components then you can also use Theme.MaterialComponents.DayNight from their v1.1.0 release. The rest of this post remains the same.

You then need to enable the feature in your app. You do that by calling AppCompatDelegate.setDefaultNightMode(), which takes one of the follow values:

  • MODE_NIGHT_NO. Always use the day (light) theme.
  • MODE_NIGHT_YES. Always use the night (dark) theme.
  • MODE_NIGHT_FOLLOW_SYSTEM (default). This setting follows the system’s setting, which on Android Pie and above is a system setting (more on this below).
  • MODE_NIGHT_AUTO_BATTERY. Changes to dark when the device has its ‘Battery Saver’ feature enabled, light otherwise. ✨New in v1.1.0-alpha03.

  • MODE_NIGHT_AUTO_TIME & MODE_NIGHT_AUTO. Changes between day/night based on the time of day. ⛔ Deprecated in v1.1.0-alpha03.

The method is static, therefore you can call it at any time. The value you set is not persisted, therefore you need to set it every time your app process is brought up. I would recommend setting it in your application class (if you have one) like so:

public class MyApplication extends Application {

    public void onCreate() {
        super.onCreate();

        AppCompatDelegate.setDefaultNightMode(
            AppCompatDelegate.MODE_NIGHT_YES);
    }
}

setLocalNightMode()

You can also override the default value in each component with a call to its AppCompatDelegate’s setLocalNightMode(). This is handy when you know that only some components should use the DayNight functionality, or for development so that you don’t have to sit and wait for night to fall to test your layout.

If you call this method and a theme change is required, it will recreate your Activity automatically by calling recreate(), so that the new theme can be applied. This is a good opportunity to test whether your Activity + Fragments save their instance state correctly.

How can I check what configuration I’m currently in?

You can easily do this by checking your resource configuration:

int currentNightMode = getResources().getConfiguration().uiMode
        & Configuration.UI_MODE_NIGHT_MASK

switch (currentNightMode) {
    case Configuration.[UI_MODE_NIGHT_NO](https://developer.android.com/reference/android/content/res/Configuration.html#UI_MODE_NIGHT_NO):
        // Night mode is not active, we're in day time
    case Configuration.[UI_MODE_NIGHT_](https://developer.android.com/reference/android/content/res/Configuration.html#UI_MODE_NIGHT_NO)YES:
        // Night mode is active, we're at night!
    case Configuration.[UI_MODE_NIGHT_](https://developer.android.com/reference/android/content/res/Configuration.html#UI_MODE_NIGHT_NO)UNDEFINED:
        // We don't know what mode we're in, assume notnight
}

WebViews

There is currently one big caveat to using this feature: WebViews. Since they can not use theme attributes, and you rarely have control over any web content’s styling, there is a high probability that your WebViews will be too contrasting against your dynamic themed app. So make sure you test your app in both modes to ensure that it’s not annoying to the user.

System night mode

Android Pie onward has a system night mode which can be enabled in ‘Developer Options’. When running on Android Pie or later, you should default to using MODE_NIGHT_FOLLOW_SYSTEM, to let the user’s device setting work.

In-app setting

It is recommended to provide a way for the user to override the default theme in your app. The recommended options and strings are:

  • ‘Light’ (MODE_NIGHT_NO)
  • ‘Dark’ (MODE_NIGHT_YES)
  • ‘Set by Battery Saver’ (MODE_NIGHT_AUTO_BATTERY)
  • ‘Use system default’ (MODE_NIGHT_FOLLOW_SYSTEM). Only show on API 28+. This should be your app’s default when shown.

A common way to do to implement would be via a ListPreference.

Updating your themes + styles

As well as calling AppCompat, you will likely need to do some work to update your themes, styles and layouts so that they work seamlessly across both dark and light themes.

The rule-of-thumb for these things is to always use theme attributes when you can. Here are the most important to know about:

  • ?android:attr/textColorPrimary. General purpose text color. Will be near-black on light theme, near-white on dark themes. Contains a disabled state.
  • ?attr/colorControlNormal. General-purpose icon color. Contains a disabled state.

Using Material Design Components also makes this a lot easier, as it’s attributes (such as ?attr/colorSurface and ?attr/colorOnSurface) provide you an easy generalized themed color to use. These attributes of course can be customized in your theme.

Using your own resources for dark/light

AppCompat in simple terms is just enabling the use of the night and notnight resource qualifiers. These have actually been available in the platform since API 8, but were previously only used in very specific scenarios.

Under the hood Theme.AppCompat.DayNight is implemented as so:

res/values/themes.xml

<style name="Theme.AppCompat.DayNight" 
       parent="Theme.AppCompat.Light" />

res/values-night/themes.xml

<style name="Theme.AppCompat.DayNight" 
       parent="Theme.AppCompat" />

This means that you can also provide alternative resources for your light and dark UIs. Just use the -night qualifier on your resource folders: drawable-night, values-night, etc.