Changing Theme Colors in Jetpack Compose: A Quick Guide

Jetpack Compose has revolutionized Android UI development by introducing a declarative approach that simplifies creating dynamic and beautiful interfaces. One of its core strengths lies in its robust theming capabilities, allowing developers to craft cohesive and visually appealing app designs. In this guide, we’ll dive into the details of changing theme colors in Jetpack Compose, exploring best practices and advanced techniques for leveraging theming to its fullest potential.

Understanding Jetpack Compose Themes

Jetpack Compose theming is centered around the Material Design system, which ensures consistency in appearance and usability across Android applications. Themes in Compose are defined through MaterialTheme, a composable function that provides easy access to design tokens like colors, typography, and shapes.

Key Components of MaterialTheme

  1. Colors: Represents the app’s color scheme, including primary, secondary, background, and surface colors.

  2. Typography: Manages the text styles used across the app.

  3. Shapes: Defines the corner styles for components like buttons and cards.

The focus of this guide is the Colors parameter and how you can effectively customize and update it.

Setting Up Theme Colors

Default MaterialTheme Colors

When you create a new Jetpack Compose project, Android Studio generates a default theme with pre-defined colors. These colors are specified in a Color.kt file and applied in the Theme.kt file.

Example of Default Colors in Color.kt

val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)

Applying Colors in Theme.kt

private val LightColorPalette = lightColors(
    primary = Purple500,
    primaryVariant = Purple700,
    secondary = Teal200
)

private val DarkColorPalette = darkColors(
    primary = Purple200,
    primaryVariant = Purple700,
    secondary = Teal200
)

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

Customizing Theme Colors

To change the theme colors, modify the Color.kt file with your desired color values. Use tools like the Material Design Color Tool or other color palette generators to ensure a harmonious scheme.

Updating Colors

val CustomBlue = Color(0xFF1E88E5)
val CustomGreen = Color(0xFF43A047)
val CustomYellow = Color(0xFFFDD835)

Then update the light and dark color palettes in Theme.kt:

private val LightColorPalette = lightColors(
    primary = CustomBlue,
    primaryVariant = CustomGreen,
    secondary = CustomYellow
)

private val DarkColorPalette = darkColors(
    primary = CustomBlue,
    primaryVariant = CustomGreen,
    secondary = CustomYellow
)

Dynamic Theme Switching

Jetpack Compose makes it easy to switch themes dynamically at runtime. This is especially useful for apps that allow users to toggle between light and dark modes or customize their UI preferences.

Example: Implementing Theme Toggle

  1. Add a state to track the current theme:

var isDarkTheme by remember { mutableStateOf(false) }
  1. Provide a toggle mechanism, such as a Switch or Button:

Switch(
    checked = isDarkTheme,
    onCheckedChange = { isDarkTheme = it }
)
  1. Pass the theme state to MyAppTheme:

MyAppTheme(darkTheme = isDarkTheme) {
    // Content here
}

Best Practices for Theming in Compose

  1. Use Semantic Color Names: Instead of naming colors based on their appearance (e.g., Blue500), use semantic names like PrimaryColor or ErrorColor to improve code readability and maintainability.

  2. Leverage Dynamic Colors (Android 12+): With Android 12’s Material You, you can extract colors from the user’s wallpaper to create a personalized theme. Use DynamicTheme to implement this feature.

    @Composable
    fun MyDynamicTheme(content: @Composable () -> Unit) {
        val dynamicColors = if (isSystemInDarkTheme()) {
            dynamicDarkColorScheme(LocalContext.current)
        } else {
            dynamicLightColorScheme(LocalContext.current)
        }
    
        MaterialTheme(
            colorScheme = dynamicColors,
            content = content
        )
    }
  3. Avoid Hardcoding Colors in UI Components: Always use theme-based colors (MaterialTheme.colors.primary) instead of hardcoding values to ensure consistent theming and easier updates.

  4. Test Across Light and Dark Modes: Use the Android Studio Preview to test your UI in both light and dark themes. Annotate your composables with @Preview:

    @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
    @Composable
    fun DarkModePreview() {
        MyAppTheme(darkTheme = true) {
            // Content here
        }
    }
    
    @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
    @Composable
    fun LightModePreview() {
        MyAppTheme(darkTheme = false) {
            // Content here
        }
    }
  5. Use Extensions for Common Themes: Encapsulate repetitive theming logic into reusable functions or extensions. For example:

    @Composable
    fun MyButton(text: String, onClick: () -> Unit) {
        Button(
            onClick = onClick,
            colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary)
        ) {
            Text(text)
        }
    }

Advanced Theming Use Cases

Per-Screen Theming

In some apps, you may want specific screens to have unique themes. Achieve this by overriding the MaterialTheme within the screen’s composable:

@Composable
fun CustomScreen() {
    MaterialTheme(colors = customColorPalette) {
        // Screen-specific content
    }
}

Animating Theme Changes

Enhance the user experience by animating theme transitions when switching themes. Use animateColorAsState:

val animatedPrimaryColor by animateColorAsState(
    targetValue = if (isDarkTheme) DarkColorPalette.primary else LightColorPalette.primary
)

MaterialTheme(colors = colors.copy(primary = animatedPrimaryColor)) {
    // Content here
}

Conclusion

Customizing and dynamically changing theme colors in Jetpack Compose is a powerful way to elevate your app’s visual appeal and user experience. By leveraging MaterialTheme, best practices, and advanced techniques like dynamic theming and animations, you can create polished, adaptable interfaces that stand out. With the flexibility Compose offers, the possibilities are virtually limitless—experiment and make your app’s UI truly shine.