We have a library with a few screens and want to g...
# compose
l
We have a library with a few screens and want to give the client apps the possibility to customize our Material theme (override some of the colors/fonts etc.). What is the best practice here? I was thinking about
Copy code
open class LibraryThemeHolder {
    
    @Composable
    open fun LibraryTheme(content: @Composable () -> Unit) {
        MaterialTheme(
            colorScheme = libraryColorScheme(),
            typography = libraryTypography(),
            content = content
        )
    }

    open fun libraryColorScheme() {
        //...
    }

    open fun libraryTypography() {
        // ...
    }   
}
It has the disadvantage of having to access the theme through an object. Are there better solutions?
s
Perhaps do what the material components do as well. Have a
FooColors
object, which defaults to whatever theme you got in the library https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]=ButtonDefaults&ss=androidx%2Fplatform%2Fframeworks%2Fsupport and https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]=ButtonDefaults&ss=androidx%2Fplatform%2Fframeworks%2Fsupport But then allow your callers to call
ButtonDefaults.textButtonColors(TheirTheme.colorScheme.primary)
and so on
l
That's a lot of overhead, putting this in every Composable
I was thinking of just injecting this in the theme somehow, and from there the library screen composables would take it over throught the screens. With XML it was simple, you just overwrote the styles in the client app.
s
Since your library seems to just use
MaterialTheme
anyway, you can try and default to fetching the client’s material theme from the composition local, and if it’s not there (if the client does not wrap their composables with MaterialTheme) then use some sane defaults from the library? Is this kinda what you want to do?
Now since it comes with a default as I see here https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:tv/tv-ma[…]ColorScheme&sq=&ss=androidx%2Fplatform%2Fframeworks%2Fsupport it may be tricky to know if the client does not wrap material theme or if they just have kept the default values hmm
l
Yes, I'm using the
MaterialTheme
in the library. Library screens are under my complete control (the client doesn't modify/access the composables there, I just wrap them in an activity which the client starts). I want to give the client the possibility to overwrite some parameters of my theme (colors/text styles) and the rest should be defaults from the library.
I don't care about the client's theme. It is independent. The client should be able to inject custom parameters in my theme.
s
Is it an option for you to at some point provide the client an input of the entire colorScheme, and make a function that takes in all the parameters in a function which has all the parameters filled with the deafult color from your library. So they can optionally only send in the override for specific colors they want to override?
Something like
Copy code
fun lightColorScheme(
    primary: Color = YourLibraryTokens.Primary,
    onPrimary: Color = YourLibraryTokens.OnPrimary,
    primaryContainer: Color = YourLibraryTokens.PrimaryContainer,
    onPrimaryContainer: Color = YourLibraryTokens.OnPrimaryContainer,
    inversePrimary: Color = YourLibraryTokens.InversePrimary,
    secondary: Color = YourLibraryTokens.Secondary,
    onSecondary: Color = YourLibraryTokens.OnSecondary,
    secondaryContainer: Color = YourLibraryTokens.SecondaryContainer,
    onSecondaryContainer: Color = YourLibraryTokens.OnSecondaryContainer,
    tertiary: Color = YourLibraryTokens.Tertiary,
    onTertiary: Color = YourLibraryTokens.OnTertiary,
    tertiaryContainer: Color = YourLibraryTokens.TertiaryContainer,
    onTertiaryContainer: Color = YourLibraryTokens.OnTertiaryContainer,
    background: Color = YourLibraryTokens.Background,
    onBackground: Color = YourLibraryTokens.OnBackground,
    surface: Color = YourLibraryTokens.Surface,
    onSurface: Color = YourLibraryTokens.OnSurface,
    surfaceVariant: Color = YourLibraryTokens.SurfaceVariant,
    onSurfaceVariant: Color = YourLibraryTokens.OnSurfaceVariant,
    surfaceTint: Color = primary,
    inverseSurface: Color = YourLibraryTokens.InverseSurface,
    inverseOnSurface: Color = YourLibraryTokens.InverseOnSurface,
    error: Color = YourLibraryTokens.Error,
    onError: Color = YourLibraryTokens.OnError,
    errorContainer: Color = YourLibraryTokens.ErrorContainer,
    onErrorContainer: Color = YourLibraryTokens.OnErrorContainer,
    outline: Color = YourLibraryTokens.Outline,
    outlineVariant: Color = YourLibraryTokens.OutlineVariant,
    scrim: Color = YourLibraryTokens.Scrim,
    surfaceBright: Color = YourLibraryTokens.SurfaceBright,
    surfaceContainer: Color = YourLibraryTokens.SurfaceContainer,
    surfaceContainerHigh: Color = YourLibraryTokens.SurfaceContainerHigh,
    surfaceContainerHighest: Color = YourLibraryTokens.SurfaceContainerHighest,
    surfaceContainerLow: Color = YourLibraryTokens.SurfaceContainerLow,
    surfaceContainerLowest: Color = YourLibraryTokens.SurfaceContainerLowest,
    surfaceDim: Color = YourLibraryTokens.SurfaceDim,
): ColorScheme
or whatever?
l
Yes, using default arguments in a function would be an option