Dynamic Color (Material You) in Compose — Wallpaper-Based Theming

Learn how to implement Material You dynamic theming in Compose, adapting colors based on device wallpaper.

Dynamic Color Schemes (API 31+)

@Composable
fun DynamicColorTheme(
    content: @Composable () -> Unit
) {
    val colorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        val context = LocalContext.current
        if (isSystemInDarkTheme()) {
            dynamicDarkColorScheme(context)
        } else {
            dynamicLightColorScheme(context)
        }
    } else {
        // Fallback for API < 31
        if (isSystemInDarkTheme()) {
            darkColorScheme()
        } else {
            lightColorScheme()
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        content = content
    )
}

Color Fallback for Older APIs

fun getColorScheme(context: Context, isDark: Boolean): ColorScheme {
    return when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            if (isDark) {
                dynamicDarkColorScheme(context)
            } else {
                dynamicLightColorScheme(context)
            }
        }
        else -> {
            // Custom fallback palette
            if (isDark) {
                darkColorScheme(
                    primary = Color(0xFF6200EE),
                    secondary = Color(0xFF03DAC6),
                    tertiary = Color(0xFF03DAC5)
                )
            } else {
                lightColorScheme(
                    primary = Color(0xFF6200EE),
                    secondary = Color(0xFF03DAC6),
                    tertiary = Color(0xFF1F6Feb)
                )
            }
        }
    }
}

Using ColorScheme Properties

@Composable
fun ColoredComponents() {
    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp)
    ) {
        // Primary container button
        Button(
            onClick = {},
            colors = ButtonDefaults.buttonColors(
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                contentColor = MaterialTheme.colorScheme.onPrimaryContainer
            )
        ) {
            Text("Primary Container")
        }

        Spacer(modifier = Modifier.height(8.dp))

        // Secondary container card
        Card(
            colors = CardDefaults.cardColors(
                containerColor = MaterialTheme.colorScheme.secondaryContainer
            ),
            modifier = Modifier.fillMaxWidth().padding(8.dp)
        ) {
            Text(
                "Secondary Container",
                modifier = Modifier.padding(16.dp),
                color = MaterialTheme.colorScheme.onSecondaryContainer
            )
        }

        Spacer(modifier = Modifier.height(8.dp))

        // Tertiary container
        Surface(
            color = MaterialTheme.colorScheme.tertiaryContainer,
            modifier = Modifier.fillMaxWidth().padding(8.dp)
        ) {
            Text(
                "Tertiary Container",
                modifier = Modifier.padding(16.dp),
                color = MaterialTheme.colorScheme.onTertiaryContainer
            )
        }

        Spacer(modifier = Modifier.height(8.dp))

        // Surface variant
        Surface(
            color = MaterialTheme.colorScheme.surfaceVariant,
            modifier = Modifier.fillMaxWidth().padding(8.dp)
        ) {
            Text(
                "Surface Variant",
                modifier = Modifier.padding(16.dp),
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
        }
    }
}

Theme Settings UI with Toggles

@Composable
fun ThemeSettingsScreen() {
    var isDarkMode by rememberSaveable { mutableStateOf(isSystemInDarkTheme()) }
    var useDynamicColor by rememberSaveable { mutableStateOf(true) }
    val context = LocalContext.current

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            "Theme Settings",
            style = MaterialTheme.typography.headlineSmall,
            modifier = Modifier.padding(bottom = 16.dp)
        )

        // Dark mode toggle
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text("Dark Mode")
            Switch(
                checked = isDarkMode,
                onCheckedChange = { isDarkMode = it }
            )
        }

        Divider(modifier = Modifier.padding(vertical = 8.dp))

        // Dynamic color toggle (API 31+)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(8.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text("Use Dynamic Color")
                Switch(
                    checked = useDynamicColor,
                    onCheckedChange = { useDynamicColor = it }
                )
            }

            Divider(modifier = Modifier.padding(vertical = 8.dp))
        }

        // Color preview
        Text(
            "Color Preview",
            style = MaterialTheme.typography.titleMedium,
            modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
        )

        LazyRow(
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(
                listOf(
                    "Primary" to MaterialTheme.colorScheme.primary,
                    "Secondary" to MaterialTheme.colorScheme.secondary,
                    "Tertiary" to MaterialTheme.colorScheme.tertiary,
                    "Primary Container" to MaterialTheme.colorScheme.primaryContainer,
                    "Secondary Container" to MaterialTheme.colorScheme.secondaryContainer
                )
            ) { (label, color) ->
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Surface(
                        color = color,
                        shape = RoundedCornerShape(8.dp),
                        modifier = Modifier.size(60.dp)
                    ) {}
                    Text(label, style = MaterialTheme.typography.labelSmall)
                }
            }
        }
    }
}

Leverage Material You to create personalized, adaptive themes that delight users!

Get 8 Android app templates: Gumroad

Leave a Reply