Dynamic Color (Material You)
Starting with Android 12, the system can generate an entire ColorScheme from the colors
in the user's wallpaper — this is "Material You," and it's why the same app can look orange on one
phone and teal on another without a single line of per-user code. This lesson wires that system
capability into RecipeBox's existing RecipeBoxTheme, alongside the static orange
lightColorScheme()/darkColorScheme() pair from two lessons ago, which stays
in place as the fallback for every device that can't generate dynamic color.
dynamicLightColorScheme() and dynamicDarkColorScheme()
These two functions live in the Material 3 library and both take an Android Context,
reading the device's current wallpaper-derived palette and returning a complete ColorScheme
— the light and dark variant of the same generated palette:
Signatures
fun dynamicLightColorScheme(context: Context): ColorScheme
fun dynamicDarkColorScheme(context: Context): ColorScheme
Both only work on API 31 (Android 12) and above — the underlying wallpaper color extraction simply doesn't exist on earlier platform versions, which is exactly why this feature needs a version check and a fallback, never a direct unconditional call.
Gating on Build.VERSION.SDK_INT
Build.VERSION_CODES.S is the named constant for API 31. Checking
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S before calling either dynamic function is
required — calling them on an older device would crash, since the classes they depend on don't exist
there yet:
Theme/Theme.kt — combining static and dynamic color
@Composable
fun RecipeBoxTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val context = LocalContext.current
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> RecipeBoxDarkColors
else -> RecipeBoxLightColors
}
MaterialTheme(
colorScheme = colorScheme,
typography = RecipeBoxTypography,
content = content
)
}
The dynamicColor parameter defaults to true but stays a parameter, not a
hardcoded check, on purpose — it gives RecipeBox's Settings screen a place to hang a "Use Wallpaper
Colors" toggle later, letting a user opt back into the fixed orange brand color even on a device that
supports Material You.
Why the Static Fallback Still Matters
Dynamic color is genuinely optional — RecipeBox's brand identity is the warm orange
RecipeBoxLightColors/RecipeBoxDarkColors pair built earlier in this section,
and that pair is what every pre-Android-12 device, and every device where dynamicColor is
turned off, continues to see. Nothing about the earlier lessons' work is wasted — this lesson only
adds a branch in front of it.
| Condition | ColorScheme used |
|---|---|
API 31+, dynamicColor = true, light mode | dynamicLightColorScheme(context) |
API 31+, dynamicColor = true, dark mode | dynamicDarkColorScheme(context) |
API < 31, or dynamicColor = false, light mode | RecipeBoxLightColors |
API < 31, or dynamicColor = false, dark mode | RecipeBoxDarkColors |
Wiring It Up at the App Root
The call site barely changes from the previous lesson — RecipeBoxTheme's new parameter
simply defaults sensibly, so most screens don't need to think about dynamic color at all:
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// dynamicColor defaults to true; falls back automatically below API 31
RecipeBoxTheme {
RecipeApp()
}
}
}
}