Dynamic Colors in Compose: A Jetpack Compose Guide

Jetpack Compose has revolutionized Android UI development by providing a declarative and reactive way to build user interfaces. One of the most exciting features is its support for dynamic colors, which allows your app to adapt its color scheme based on various factors, such as user preferences or the system theme. Using dynamic colors, you can provide a personalized and cohesive user experience across different devices and themes.

What are Dynamic Colors?

Dynamic colors are color schemes that change based on contextual inputs, such as the user’s wallpaper or system theme. This allows apps to feel more integrated with the device’s overall aesthetic, enhancing user satisfaction and visual appeal. Dynamic colors are particularly useful in Android 12 and above, where the Material You design system leverages them extensively.

Why Use Dynamic Colors?

  • Enhanced User Experience: Provides a cohesive and personalized look by adapting to the user’s theme.
  • Accessibility: Improves readability and accessibility by adjusting colors to ensure sufficient contrast.
  • Modern Design: Aligns with the Material You design principles, offering a fresh and modern UI.

How to Implement Dynamic Colors in Jetpack Compose

Implementing dynamic colors in Jetpack Compose involves utilizing the MaterialTheme and the dynamicLightColorScheme and dynamicDarkColorScheme functions.

Step 1: Add Material 3 Dependency

First, ensure you have the Material 3 dependency in your build.gradle file:

dependencies {
    implementation("androidx.compose.material3:material3:1.1.2") // Use the latest version
}

Step 2: Create a Compose Theme with Dynamic Colors

Define a custom Compose theme that uses dynamic color schemes:


import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            val window = (view.context as Activity).window
            window.statusBarColor = colorScheme.primary.toArgb()
            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

In this code:

  • We check if dynamic color support is available (Android 12 and above).
  • If dynamic color is supported, we use dynamicLightColorScheme or dynamicDarkColorScheme based on the current theme.
  • If dynamic color is not supported, we fall back to predefined light or dark color schemes.
  • The MaterialTheme composable applies the chosen color scheme to its content.

Step 3: Use the Custom Theme in Your App

Wrap your composable content with the custom theme:


import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MyScreenContent() {
    Text(text = "Hello, Dynamic Colors!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyAppTheme {
        MyScreenContent()
    }
}

Now, run your app on an Android 12+ device, and you should see the colors adapt based on your device’s theme or wallpaper.

Advanced Customization

For further customization, you can also define your own color schemes and map dynamic colors to your custom colors.

Example of Custom Color Mapping


import androidx.compose.ui.graphics.Color

// Custom colors
val customPrimaryColor = Color(0xFF6200EE)
val customSecondaryColor = Color(0xFF03DAC5)

private val DarkColorScheme = darkColorScheme(
    primary = customPrimaryColor,
    secondary = customSecondaryColor,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = customPrimaryColor,
    secondary = customSecondaryColor,
    tertiary = Pink40

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

Handling Different Android Versions

Since dynamic colors are available from Android 12 (API level 31), you may need to handle older versions differently to maintain a consistent UI.


import android.os.Build
import androidx.compose.runtime.Composable

@Composable
fun MyThemedContent(content: @Composable () -> Unit) {
    val useDynamicColors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
    
    MyAppTheme(dynamicColor = useDynamicColors) {
        content()
    }
}

By using the above snippet, you ensure that dynamic colors are used only on devices that support them, falling back to your default color scheme on older devices.

Conclusion

Dynamic colors in Jetpack Compose offer a seamless way to create visually appealing and user-centric applications that adapt to the user’s device settings. By incorporating dynamic colors, you not only provide a modern user experience but also improve the overall accessibility and integration of your app within the Android ecosystem. Utilizing dynamicLightColorScheme and dynamicDarkColorScheme ensures that your application aligns with Material You’s design principles, creating a cohesive and delightful user interface.