Search code examples
androidkotlinsingletonthemesandroid-jetpack-compose

How to make Jetpack Compose theme customizable?


I'm working on a Design System with Jetpack Compose, I have a lot of components all using my theme to access the colors, typography and dimensions. Now I'm looking into the possibility of allowing customization. For instance, overriding a color resulting in this to change for every component.

What I've attempted so far adding parameters to fun MyTheme, I have had some success with this approach, but the problem is that any dev could modify the theme at any time like this. I would like for it to be customizable only once, maybe like a singleton. Any ideas or recommendations?

Here is my theme:

private val LightColorPalette = LightColors()
private val DarkColorPalette = DarkColors()

private val LocalColors = compositionLocalOf<MyColors> {
    error("No typography provided! Make sure to wrap all usages of this components in a MyTheme.")
}

private val LocalTypography = compositionLocalOf<MyTypography> {
    error("No typography provided! Make sure to wrap all usages of this components in a MyTheme.")
}

private val LocalDimensions = staticCompositionLocalOf { MyDimensions() }

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    colors: MyColors = if (darkTheme) DarkColorPalette else LightColorPalette,
    typography: MyTypography = getTypography(LocalConfiguration.current),
    dimensions: MyDimensions = getDimensions(LocalConfiguration.current),
    content: @Composable () -> Unit
) {
    val shapes = MaterialShapes

    CompositionLocalProvider(
        LocalTypography provides typography,
        LocalColors provides colors,
        LocalDimensions provides dimensions,
    ) {
        MaterialTheme(
            colors = debugColors(darkTheme),
            content = content,
            shapes = shapes
        )
    }
}

Solution

  • I managed to get this working by making all my CompositionLocal variables public. With this, any new theme that overrides this variables with CompositionLocalProvider will have the composables under it using this new values.

    @Composable
    fun SecondaryTheme(
        content: @Composable () -> Unit
    ) {
        val configuration = LocalConfiguration.current
    
        val colors = SecondaryColors()
        val typography = getDefaultTypography(configuration)
        val dimensions = getDefaultDimensions(configuration)
        val shapes = MaterialShapes
    
        CompositionLocalProvider(
            LocalTypography provides typography,
            LocalColors provides colors,
            LocalDimensions provides dimensions,
        ) {
            MaterialTheme(
                colors = materialColors(darkTheme),
                content = content,
                shapes = shapes
            )
        }
    }
    

    I recommend checking the official documentation