Blog Infos
Author
Published
Topics
, , , ,
Published

Jetpack Compose has revolutionized how we build UIs in Android, offering a more declarative and intuitive approach. As apps grow more complex, maintaining a consistent look and feel across screens becomes crucial. This is where theming in Jetpack Compose shines, allowing you to define colors, typography, and shapes centrally and apply them across your entire app with ease.

In this article, we’ll explore best practices for defining colors in Jetpack Compose, covering everything from the standard Material color schemes to creating custom palettes and integrating dynamic colors. By the end, you’ll be equipped to create flexible and scalable themes that make your UI not only look great but also consistent and easy to maintain.

MaterialTheme in Jetpack Compose

Compose is built around the Material Design principles. The MaterialTheme is a composable that provides a central place for defining your app’s color scheme, typography, and shapes.

Here’s a basic structure of a theme in Compose:

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

    MaterialTheme(
            colors = colors,
            typography = AppTypography,
            shapes = AppShapes,
            content = content
        )
   }

With this setup, your theme adapts dynamically based on whether the user prefers light or dark mode. Defining your theme this way ensures that all your UI components are consistently styled.

Defining Your Color Palette

In Jetpack Compose, the ColorPalette is where you define the essential colors for your app’s theme, such as primary, secondary, etc. A best practice when defining these colors is to centralize them in the dedicated Color.kt file. Organizing your color definitions this way keeps your codebase clean and makes it easier to manage and update your color palette as your app evolves.

Here’s an example of defining light and dark color palettes:

// Color.kt
private val lightColorPalette = lightColors(
    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    primaryContainer = md_theme_light_primaryContainer,
    onPrimaryContainer = md_theme_light_onPrimaryContainer,
    secondary = md_theme_light_secondary,
    onSecondary = md_theme_light_onSecondary,
...
)

private val darkColorPalette = darkColors(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    primaryContainer = md_theme_dark_primaryContainer,
    onPrimaryContainer = md_theme_dark_onPrimaryContainer,
    secondary = md_theme_dark_secondary,
    onSecondary = md_theme_dark_onSecondary,
...
)

In this setup, each color scheme is defined based on whether the app is in light or dark mode, ensuring that the appropriate colors are applied consistently across your UI. By placing these definitions in the Color.kt file, you not only improve the organization of your project but also make future updates more manageable.

Customizing Your Theme with Extended Colors

In some cases, the standard Material color scheme might not be enough for your needs. You may want to add additional colors specific to your brand or design system, like a background variant or other colors that aren’t covered by the default ColorScheme.

Jetpack Compose makes it easy to extend the theme by using a custom implementation.

@Immutable
data class ColorFamily(
    val backgroundVariant: Color,
)

@Immutable
data class ExtendedColorScheme(
    val extra: ColorFamily = extendedLight.extra,
)
val extendedLight = ExtendedColorScheme(
    extra = ColorFamily(
        backgroundVariant = Color(0xFFEEEEEE), // Example light variant
    ),
)
val extendedDark = ExtendedColorScheme(
    extra = ColorFamily(
        backgroundVariant = Color(0xFF333333), // Example dark variant
    ),
)
val LocalExColorScheme = staticCompositionLocalOf { ExtendedColorScheme() }

In this example, we create a ColorFamily data class to hold our additional colors. We then build an ExtendedColorScheme that includes this ColorFamily. The staticCompositionLocalOf function is used to make this extended color scheme available throughout the app.

Next, modify your theme composable to include the extended colors:

@Composable
fun MainTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
    val extendedColorScheme = if (darkTheme) extendedDark else extendedLight

    CompositionLocalProvider(
            LocalExColorScheme provides extendedColorScheme
        ) {
            MaterialTheme(
                colorScheme = colorScheme,
                typography = Typography,
                content = content
            )
        }
   }

You can now access your custom colors anywhere in your composables using LocalExColorScheme.current:

@Composable
fun CustomBackgroundBox() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(LocalExColorScheme.current.extra.backgroundVariant)
    ) {
        // Your content here
    }
}

This approach brings several significant benefits, particularly in terms of scalability, consistency, and flexibility. As your app evolves and your design system grows, the ability to incorporate additional custom colors becomes essential. By leveraging this method, you can easily scale your theme to include any extra colors required without disrupting the existing structure.

Moreover, the use of an extended color scheme ensures that these custom colors are consistently applied across your app. With centralized access through CompositionLocal, you eliminate discrepancies in color usage, resulting in a more cohesive and professional look throughout the entire user interface.

Flexibility is another key advantage of this approach. By integrating CompositionLocal, your theming remains adaptable to various contexts, such as different user settings, UI modes, or device configurations. Whether you need to respond dynamically to changes in theme preferences, screen modes, or specific conditions, this strategy allows for seamless adjustments.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

Integrating Dynamic Colors

Starting from Android 12 (API level 31), Jetpack Compose supports dynamic colors. This feature allows your app’s theme to adapt based on the user’s wallpaper and system settings, providing a more personalized experience.

Dynamic colors are automatically generated and applied, making it easy for your app to blend with the overall device theme.

Here’s how you can integrate dynamic colors into your theme:

@Composable
fun MainTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true, // Enable dynamic colors
    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
    }

    CompositionLocalProvider(
            LocalExColorScheme provides if (darkTheme) extendedDark else extendedLight
        ) {
            MaterialTheme(
                colorScheme = colorScheme,
                typography = Typography,
                content = content
            )
        }
   }

This integration revolves around the dynamicColor parameter, which determines whether your theme should adapt to the system’s dynamic color settings. When the parameter is enabled, and the device runs on Android 12 or higher (API level 31), Compose uses dynamicDarkColorScheme(context) or dynamicLightColorScheme(context) to automatically generate a color scheme. These functions dynamically adjust the colors based on the user’s wallpaper, creating a theme that harmonizes with both light and dark modes.

To ensure backward compatibility, the implementation first checks the device’s SDK version before applying these dynamic schemes, allowing your app to support older devices gracefully.

Why Use Dynamic Colors?

Dynamic colors in Jetpack Compose offer several advantages that enhance both the visual appeal and usability of your app. They contribute to an enriched user experience by providing seamless integration with the device’s UI, leading to a more immersive and cohesive design. By adopting dynamic colors, your app naturally aligns with the overall system theme, ensuring consistency across other apps and the broader device interface. This alignment contributes to a unified look and feel, which is particularly valuable when aiming for a polished and professional presentation. Additionally, dynamic colors simplify the theming process; instead of manually defining every color, you can leverage system-generated palettes while still retaining the flexibility to override specific elements when needed.

This feature is especially useful for apps that want to align with the latest Android design trends and offer a personalized experience based on user preferences.

A Helpful Tool for Designing Your Theme

Creating a visually cohesive and polished theme can be challenging, especially when balancing colors, typography, and brand identity. A great resource to assist with this is Material Theme Builder, a powerful online tool.

This tool allows you to:

  • Experiment with different color schemes and see how they adapt across light and dark modes.
  • Fine-tune typography and shape settings.
  • Export the generated theme directly for use in your Jetpack Compose project.

The Material Theme Builder simplifies the process of defining your color palette and typography, ensuring that your theme aligns with Material Design guidelines.

By leveraging this tool, you can quickly iterate and fine-tune your theme before integrating it into your Compose project, saving time and ensuring consistency across your app.

Conclusion

Theming in Jetpack Compose offers a powerful and flexible way to manage your app’s visual identity. By extending your color scheme, leveraging CompositionLocal, integrating dynamic colors, and utilizing resources like the Material Theme Builder, you can create a cohesive and scalable design system that’s both easy to maintain and visually appealing.

If you want to see these principles in action, feel free to explore this project where I applied the same techniques discussed in this article: Social Cleaning Control.

With these best practices in place, your Jetpack Compose apps will not only look great but also provide a consistent and enjoyable user experience.

Feel free to share your comments, or if you prefer, you can reach out to me on LinkedIn.

Have a great day!

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
Menu