Sid Patil

code
search
dark_mode
person_outline

Dynamic themes with Compose and Material 3

calendar_today favorite_border 4.4K views hourglass_empty 6 min read Compose Android code Featured in Google Developers Library

Dynamic colors with Compose and Material 3


In this article we will quickly learn how to use Jetpack Compose and Material design 3 library to build a theme for your app which uses colors generated by Material You on Android versions 12/12L and above.

This article covers:

  • Generating Light and Dark themes
  • Leveraging Jetpack Compose for dynamic behaviour
  • Building your first theme
  • Adapting your app to dynamic colors


Dynamic colors of the Android system

The Android system starting from versions 12/12L now supports what we call dynamic theming. Essentialy as a part of the new user customization centric design experience, the Android framework will now extract colors from the wallpaper set on your device.

Use wallpaper generated system colors from Android 12 and Material You to repaint your apps to the users taste by using the material design 3 library and Jetpack compose

Monet is the logic within the framework that is responsible for color extraction from the wallpaper. It’s kind of fascinating to understand how Monet works under the hood, you can read more about the internals of how Monet provides dynamic colors to the Android system.


Project dependencies

To bootstrap the project, let’s add the following dependencies to the project for Jetpack compose and material 3.

    // Compose
    implementation "androidx.compose.ui:ui:1.1.1"
    implementation "androidx.compose.ui:ui-tooling-preview:1.1.1"

    // Material 3
    implementation "androidx.compose.material3:material3:1.0.0-alpha10"

Compose Material 3 is still in alpha at the time of writing this article.


Material theme builder

If you want the easiest way to use dynamic themes, head over to the site material theme builder. Its a really cool project on Github pages, which will help us get started, without manually preparing the theme files. Further down the article we will take a look at the files generated by the theme builder and also understand how we can manually prepare the theme ourselves.

Once you are on the theme builder site, navigate to the custom colors tab, and here we will prepare the base of our Composable theme. You will find options to select core colors of your application, these colors could be the colors that define your brand or application.

Once you have entered the RGB values, you should be able to export the colors. To export, click on the export option and select Jetpack Compose (Theme.kt).

This the easiest way availble right now to kickstart your project with dynamic theming. In the exported files you will find auto-generated Theme.kt, Colors.kt and Typography.kt files.

package com.example.compose
import androidx.compose.ui.graphics.Color


val md_theme_light_primary = Color(0xFF6750A4)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFEADDFF)
val md_theme_light_onPrimaryContainer = Color(0xFF21005D)
val md_theme_light_secondary = Color(0xFF625B71)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
.
.
.
.
.

Add the files to your project structure under ui/theme package and you are good to start migrating your project colors.

Alternatively you could just head over to this Github gist for the theme files and add your custom color hexcodes as you see fit.


Building light and dark color schemes

M3 (Material 3) uses what we call as a baseline color scheme to begin with. This baseline color scheme consists of the following colors:

  1. Primary, Secondary and Tertiary
  2. On Primary, Secondary and Tertiary
  3. Primary, Secondary and Tertiary container
  4. Error, On Error, On Error Container
  5. Background, On Background
  6. Surface, On Surface and On surface container (Positive and inverse variants)
  7. Outline and shadow

Over in all, the baseline currently consists of about 26 colors / tokens for each of the theme variants, light and dark.

In our Theme.kt file we will prepare the light and dark composable color schemes. A color scheme is a state aware container class from the M3 library which holds the various colors we saw above.

import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme

private val LightThemeColors = lightColorScheme(

    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    .
    .
    inversePrimary = md_theme_light_inversePrimary,
)

private val DarkThemeColors = darkColorScheme(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    .
    .
    inversePrimary = md_theme_dark_inversePrimary,
)

Full snippet of the above

The references to the colors above are the same ones we were able to export from the material theme builder, you can also customize the colors to your app or brands design guideline.


Dynamic theming

Up until now, we have been only looking at M3 library and preparing the colors for our app’s theme. Now that we have our M3 light and dark color schemes prepared, lets look at dynamic color schemes.

This is the easier part as M3 provides easy functions for accessing dynamic wallpaper colors. The DynamicTonalPalatte of the M3 library, provides TonalPalettes which encapsulate the dynamic system colors. We will use two functions

    fun dynamicDarkColorScheme(context: Context): ColorScheme 

    fun dynamicLightColorScheme(context: Context): ColorScheme

Preparing your composable theme

To prepare your app wide theme composable, we need to be mindful of the following:

  1. Android API support
  2. System state (Dark or light mode)

We want our app to be backward compatible and design our theme in a manner that it should support dynamic colors if the API is or above SDK 31 (Android 12). We also need to take into account if the user has enabled or disabled dark mode, either manually or automatically based on time of the day.

// For checking API support, we could do a simple build SDK version check 
fun supportsDynamic() : Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S

// For checking system dark/light mode state, we use the utility from compose foundation
package androidx.compose.foundation

@Composable
@ReadOnlyComposable
fun isSystemInDarkTheme()

We can now write our AppTheme composable which wraps our entire UI, we use the above functions to check API support and enable or disable dynamic theming if SDK version in above 31 (Android 12). We also switch the color schemes to the darker palette if the system is in dark mode.

/**
 * App-wide dynamic theme
 * @param content [Composable] UI
 */
@Composable
fun AppTheme(content: @Composable() () -> Unit) {

    val inDarkMode: Boolean = isSystemInDarkTheme()

    val colors = if (supportsDynamic()) {
        val context = LocalContext.current
        if (inDarkMode) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
    } else {
        if (inDarkMode) DarkThemeColors else LightThemeColors
    }

    MaterialTheme(
        colorScheme = colors,
        typography = AppTypography,
        content = content
    )
}

To use your theme, simply call it at the top layer / level of your screen

setContent {
    AppTheme {
        HomeScreen()
    }
}

Footnotes

We have learnt how to leverage M3 components, Jetpack compose and utilize the APIs on Android 12/12L and Tiramisu to build an app wide theme. ๐Ÿ™Œ๐Ÿผ ๐ŸŽ‰

Use wallpaper generated system colors from Android 12 and Material You to repaint your apps to the users taste by using the material design 3 library and Jetpack compose

A sample of material 3 components from material.io

Hope you found this article helpful, if so do share it and add any feedback or questions you may have in the comments below. Cheers!


References

  1. M3 Color system and baselines
  2. M3 builder
  3. Code lab for XML version of migration to M3
  4. Compose release notes
  5. M3 release notes

Attributions

  1. Images from Material.io

About the author

Siddhesh Patil is an Android Engineer at DeliveryHero. Building core ordering experiences at Foodpanda and Foodora. He is the organzier of Kotlin Mumbai, a developer community in Mumbai, India. Find talks, articles and latest developments in Android and Kotlin on this website siddroid.com

Sid Patil is an Android Engineer and Kotlin Advocate based in Berlin, Germany. He works at Delivery Hero, building ordering experiences for food delivery and Q-commerce brands like Foodpanda and Foodora.

Share on

book

More articles to read