Home · Android & Kotlin Tutorials

Android 10 Dark Theme: Getting Started

In this tutorial, you’ll learn how to build an app with dark theme support.

4.8/5 5 Ratings

Version

  • Kotlin 1.3, Android 10.0, Android Studio 3.5

Android 10 introduced an exciting new user feature: Dark theme. While support for dark theme is not new to Android, Android 10 introduced a system-level toggle that switches between dark and light themes. That means the theme applies to the system UI as well, not only specific apps.

Now that your users have a system-level dark theme toggle, they’ll expect your app to support it. The choice is up to you, but not supporting it may lead to angry users. Unless your app design is already dark, it’s a good idea to go ahead and add it.

There are three major benefits of implementing dark theme in your app:

  • Battery life: On devices with OLED screens, dark theme can extend battery life because the individual pixels have to do less work on dark areas of the screen.
  • Visibility: Dark theme offers a better viewing experience in low-light environments.
  • Accessibility: Dark theme is an important feature for users sensitive to bright light.
Note: This tutorial assumes that you have a solid knowledge of Android development. If you’re completely new to the topic, please check out our Beginning Android Development with Kotlin series first.

The sample app in this tutorial uses the MVVM architectural pattern. If you’re not familiar with this pattern, please go through our MVVM on Android video course to familiarize yourself with it.

The sample app also uses Koin for dependency injection. Understanding Koin isn’t necessary for this tutorial, but if you want to learn more about it, check out our Dependency Injection with Koin screencast.

Getting Started

In this tutorial, you’ll add dark theme support to the PdfFever app, which lets you read files in PDF format. It’s common for reader apps to have both light and dark themes to provide the best reading experience under various conditions and environments.

For the sake of simplicity, PdfFever comes bundled with predefined PDF files that you can read.

Android reading a pile of books

Download the materials for the tutorial by using the Download Materials button at the top or bottom of the page, and import the starter project into Android Studio. This is what the package structure of the project looks like:

PdfFeer's project structure

Take a moment to familiarize yourself with the source code.

When you’re ready, build and run the app on a device or emulator running Android 10. You should see the home screen:

PdfFeer's home screen with two PDF files

Tap on any of the listed PDF files to open the reader screen.

Pull down the Android Quick Settings menu, where you’ll see a Dark Theme toggle:

Where to turn dark theme on or off

If you don’t see it, go to the Settings app and find the Dark Theme option there.

Enable a dark theme using this switch and you’ll see that the system UI switched to dark mode, but PdfFever stayed the same. Not such a good UX, right? :]

Force Dark

The quickest solution for implementing a dark theme is Force Dark, a new feature available from Android 10 that automatically applies the dark theme to your app.

There’s a way for users to enable the dark theme in your app, even if you don’t support it: by using the Override force-dark option in the Developer options. You could preview how Force Dark will look in your app by using this option.

Instead of previewing, you’re going to implement Force Dark in PdfFever.

Force Dark runs in the rendering time and inspects the rendered views. It then decides which views it should invert to adapt to dark mode.

Before implementing Force Dark, make sure you’ve configured your project for Android 10. Check that you’ve downloaded Android 10 SDK and that you’ve set compileSdkVersion and targetSdkVersion to 29. PdfFever comes this way out of the box.

To implement Force Dark, open styles.xml and add the following tag to the AppTheme style:

<style name="AppTheme" parent="Theme.MaterialComponents.Light">
  ...

  <item name="android:forceDarkAllowed">true</item>
</style>

By adding forceDarkAllowed to the AppTheme and setting it to true, you apply Force Dark throughout the whole app. You can also add it to specific activities instead of the entire app.

That’s it. You successfully implemented Force Dark. Build and run your app and enable Dark Theme in the system settings to see the result.

Looks pretty good.

Adding Force Dark only requires one line of code, but there are two problems: You have almost no control over how your app will look after you apply it and it only works for Android 10.

If you want to disable Force Dark for specific views, you can do so by adding a android:forceDarkAllowed attribute to the view in the XML. You can also use setForceDarkAllowed() to do so programmatically.

For simple apps, Force Dark might work well enough. But while you can use it as a temporary solution until you properly implement the dark theme, it’s not sufficient for many apps. An example would be an image inside a view, this image may not look suitable if it is automatically rendered into dark mode.

The other option to add a dark theme is to implement a custom theme.

DayNight

DayNight is a theme in the AppCompat library that lets you implement a custom dark theme in your app.

DayNight enables a night resource qualifier, which means that you can use specific resource folders like values-night. It’s backwards compatible up to API 14.

To add DayNight to your app, open styles.xml.

First, comment out the line that enables the ForceDark feature.

Then, make your AppTheme extend the DayNight theme by changing the value of the parent attribute of the AppTheme style tag to Theme.MaterialComponents.DayNight:

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

You're using DayNight from the MaterialComponents library, which is a part of Android Jetpack that helps developers implement material design in their app.

If you're not familiar with material design, check out our Introduction to Material Design tutorial.

The best practice is to use MaterialComponents over the AppCompat library because it redesigns some UI elements and adds some new elements to the collection.

DayNight provides platform attributes like textColorPrimary, which automatically adapts to the dark or light theme.

For your next step, you're going to add your own colors instead of relying on platform attributes.

Adding Custom Colors

First, add the values-night directory. Switch to the Project view of the project structure. Right-click on the res folder and select NewAndroid Resource Directory.

In the pop-up window, under Available qualifiers, select Night Mode and add it to the Chosen qualifiers.

Under the Night mode drop-down, select Night.

Finally, select OK.

Setting up night values

Now, you'll see the values-night folder under res. Right-click on the values-night ► New ► Android resource file and add colors.xml as the file name.

Add following values to that file within the resources as shown:

<resources>

  <color name="ic_settings_color">#FFFFFFFF</color>
  <color name="ic_attachment_color">#FFFFFFFF</color>
  <color name="background_color">#FF808080</color>
  <color name="ic_color_lens_color">#FFFFFFFF</color>

</resources>

If you open colors.xml in the values folder, you'll see that you already have these color values defined there. Color values defined in values-night are the values your app will use when you toggle Dark Theme on.

Build and run the app and turn dark mode ON.

You'll notice that the app uses the colors specified in colors.xml from values-night when dark mode is on.

By default, when you extend from the DayNight theme, Android follows the system settings and applies a dark theme across all activities.

Sometimes, you may need different behavior. Maybe you want your app to always be in dark mode regardless of system settings, or maybe you don't want specific activities to go dark.

Luckily, Android provides you with APIs that you can use to override this behavior.

Overriding Dark Mode System Settings

Open the home screen in the PdfFever app. You'll notice the settings icon in the toolbar. Click on it.

Click on the Theme setting found there. You'll see the following dialog:

Dark theme options dialog

The user can choose from four different settings:

  • MODE_NIGHT_FOLLOW_SYSTEM: The default mode the app uses if you don't specify anything. This mode follows the system settings and applies the dark theme based on them. Since system settings are only available on the Android 10, this mode will usually default to a white theme.
  • MODE_NIGHT_NO: Always uses the light theme, regardless of the system settings.
  • MODE_NIGHT_YES: Always uses the dark theme, regardless of the system settings.
  • MODE_NIGHT_AUTO_BATTERY: Uses a dark mode when the system's Battery Saver feature is on. Otherwise, it uses a light theme. If Battery Saver is on and the user begins to charge their device, it will automatically use light mode again.

These settings correspond to the constants in the AppCompatDelegate that you can use to specify the night mode you want to use.

There are two more modes, MODE_NIGHT_AUTO_TIME and MODE_NIGHT_AUTO but they are deprecated and out of the scope of this tutorial.

Now, tap on any of these modes. Nothing happens in the UI. The selected option saves to shared preferences.

You'll need to do some work to make these changes take effect.

Open ui/settings/SettingsFragment.kt and navigate to setTheme. Add the following body to the function:

private fun setTheme(mode: Int) {
    AppCompatDelegate.setDefaultNightMode(mode)
}

setDefaultNightMode is the static method in AppCompatDelegate that you use to set one of the night modes.

You call setTheme in the SettingsFragment whenever you change the Theme setting.

The setDefaultNightMode recreates any visible activities. This wasn't the case previously, so you needed to handle that by yourself.

Click on the Settings icon on the home screen and set the Theme to Light Theme. You'll notice that the app's theme switches to the light mode right away, regardless of system settings. Try out other options, too, and see what happens.

Set the Theme back to light once you've explored the app.

Now, kill the app process by tapping the system's Recent button and remove PdfFever from the list.

Relaunch the app.

The theme is now dark, even though you set the option to light. This happens because the values set on these APIs do not persist.

Making the Settings Persistent

To fix this, you need to use setDefaultNightMode whenever the app process launches. You'll do that in the Application class.

Open ui/application/PdfFeverApplication.kt and navigate to initTheme.

Add following body to that method:

private fun initTheme() {
    val preferences = PreferenceManager.getDefaultSharedPreferences(this)
    ThemeManager.applyTheme(preferences.getString("preference_key_theme", "")!!)
}

This method will read the user's night mode preference from the shared preferences file and will use the ThemeManager helper class to apply the theme on the app process start.

Build and run.

Set the dark theme in the app's settings, if you haven't set it already. Kill the app process and relaunch the app.

Now, night mode persists.

If you want to disable the night mode only for a specific activity, use AppCompatDelegate’s setLocalNightMode and specify the mode you want.

It's important to mention that for this to work, your Activity needs to extend from the AppCompatActivity.

You can also programmatically check your app's current night mode like this:

getResources().getConfiguration().uiMode
        & Configuration.UI_MODE_NIGHT_MASK

This looks at the configuration's UI mode. You can then compare the result of this with the UI modes available to see which mode your app is in.

Other Scenarios

Integrating dark mode into PdfFever is fairly straightforward. It's a simple app.

When you do this on your own, you'll probably have a more complicated project with many screens and complex UIs. Here's a brief overview of some other scenarios you might encounter:

Icons & Illustrations

Every app has some kind of icon, like the Settings icon that you saw in PdfFever. If your icons are in a vector, they can automatically handle theme switching by referencing colors from the values resource folders instead of hard-coding the color values. Alternatively, you could add another version of the icon to the drawable-night folder to use in dark mode.

On the other hand, if you're fetching icons from the server, you'll need to handle theme switching manually. There are several ways you can do that.

Once you fetch the icon, you can tint it in your code. This works well if an icon is in a single color. If the icon is colorful, tinting might not be an ideal solution since you don't have control over which part of the icon you'll tint.

Another option is to have an icon that looks good in both dark and light themes.

Elevation

In Android, elevation is the distance between the view and its parent on the z-axis. You use it to show the importance of some view on-screen by making it visually closer to the user and to give a hint to the user that they can interact with the view.

In light themes, a shadow represents the elevation. As the elevation goes up, the shadow becomes wider.

In the dark theme, you'll probably have some dark color in the background, which makes the shadow hard to see.

To solve this problem, you use an elevation overlay.

As the surface rises on the z-axis, the surface color changes, as if there was a semi-opaque white layer on top of it. This layer gets more opaque as you rise in elevation. It blends down the color and uses it as a tint on the background color.

All of the components in the Material design components support this out of the box. While it's out of the scope of this article, you can build this behavior into your own custom components.

WebView

WebView works seamlessly with Force Dark by applying dark theme to web content as well. If the WebView parent view is being automatically force darkened, then the WebView's content will be displayed so as to emulate a dark theme. Only WebViews attached to the view hierarchy will be inverted.

Lottie Animations

Lottie Animations do not use color resources. Instead, colors are changed at runtime using dynamic properties by inlining them in JSON files.

You can check out Lottie's official documentation to see how that is done.

Maps

MapView doesn't support dark theme out of the box, but it supports custom map styling functionality. This lets you specify the colors of a map via JSON file. You utilize this feature by adding different JSON files to the raw-night folder used in dark mode.

Android on a map

Notifications

Notification templates support dark theme out of the box. If you're using a custom layout for your notification, then you add dark theme support by using the same styling techniques that you use for a regular layout.

Android layout

Where to Go From Here?

Good job finishing the tutorial! You learned how to add dark theme support to your app and how to make it backward-compatible.

To learn more about dark theme, check out our Supporting Dark Theme video course or you can check out the official Android documentation.

I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!

Average Rating

4.8/5

Add a rating for this content

5 ratings

More like this

Contributors

Comments