AppCompat — DayNight

The DayNight functuonality in AppCompat allow your app to easily switch between Theme.AppCompat (dark) and Theme.AppCompat.Light (light) depending on a number of factors. 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?

It’s simple to use, you just 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>

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

  • MODE_NIGHT_NO. Always use the day (light) theme.
  • MODE_NIGHT_YES. Always use the night (dark) theme.
  • MODE_NIGHT_AUTO. Changes between day/night based on the time of day.
  • 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).

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 a static block in your application class (if you have one), or your Activities like so:

static {
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_AUTO);
}
public class MyApplication extends Application {

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.

How can I check what mode my app is using?

You can easily do this by checking your resource configuration:

int currentNightMode = getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MAS
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
// Night mode is not active, we're in day time
case Configuration.UI_MODE_NIGHT_YES:
// Night mode is active, we're at night!
case Configuration.UI_MODE_NIGHT_UNDEFINED:
// We don't know what mode we're in, assume notnight
}

My app looks weird

Aka I can’t see any text (dark-on-dark). Aka my icons are the wrong color.

Since this functionality changes the theme of your app, you need to make sure that your layouts/styles/drawables work in both light and dark environments.

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.

WebViews

There is one big caveat to 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.

Location location location

When using MODE_NIGHT_AUTO, we need to use the device location to be able to calculate sensible times to switch between day or night. This might be sound scary, but don’t fear. If your app already has location permissions granted, AppCompat will try and grab the last known location from LocationManager and use those to calculate the local sunrise and sunset.

If your app has not been granted those permissions, or there simply isn’t a last known location, then we use some fixed values. These are currently 6 am for sunrise and 10 pm for sunset.

Why didn’t you just make AUTO the default?

There are a few reasons which are hopefully clearer after hearing the imagined scenario for using this:

  1. Change your theme to extend from Theme.AppCompat.DayNight.
  2. Add a setting in your app allowing the user to opt-in. At this point store the setting (probably in a SharedPreference). Call setDefaultNightMode() with the chosen value.

Then the next time your app is launched, read the stored value and call setDefaultNightMode() again with the chosen value.

So the main reason is that we do not want your user to update your app, and suddenly find that it changes color randomly (to them) based on the time of day.

Android Pie night mode

Android Pie now has its own night mode, along with a user controlled setting for enabling it. If you’re running on Android Pie, you should default to using MODE_NIGHT_FOLLOW_SYSTEM, to let the user’s global device setting work in your app.

Only change to the other options if the user requests it (via a preference or UI action). Also make sure to add a way for the user to revert back to MODE_NIGHT_FOLLOW_SYSTEM. A possible title is ’Use system setting’.

Can I use my own resources for day/night switching?

Yes, you can. AppCompat in simple terms just enables the use of the night mode resource qualifiers at any time. These have actually been available in the platform since API 8, but were only used in a very specific scenario: in car mode and docked.

So under the hood Theme.AppCompat.DayNight is just 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 your resources for day/night time. Just use the -night qualifier on your resource folder: drawable-night, values-night, etc.

Night night

So there we go. We don’t expect this feature to be applicable or useful to every app, but it can be powerful when used appropriately.