One Off the Slack: What Is a Theme, Anyway?

Anton Spaans (a.k.a., Streets of Boston) asked:

What material compose component can be used for a Label, a read-only TextField (no cursor, no focus, no keyboard pop-up)?

This led to some discussion of what the concern is about “material”, particularly since Text() is how one provides read-only text in Compose UI. As Zach Klippenstein noted:

Text is already kind of material, it interacts with material colors

Anton narrowed down the problem:

Text does pick up the color in the app itself. But it has trouble in the preview in the IDE

The crux of the issue is how theming works. We often see code like this:

MaterialTheme {
  // TODO composables go here
}

Whenever you see a composable, like MaterialTheme(), that seems to be creating side effects (e.g., affecting colors of child calls), the answer probably is that it is configuring an ambient. And, at least with the current implementation of MaterialTheme(), that is exactly what it does. MaterialTheme() takes any provided values for colors, typography, and so on, and shoves them into ambients.

Ambients work on a call tree basis. Child calls can override ambients, but those overrides only affect that branch of the call tree. As a result, top-level calls to things like MaterialTheme() affect most of your app, but you can alter the ambients at lower levels to make changes for specific screens or composables.

This then can cause problems with @Preview. If you are trying to preview some deeply-nested composable, that composable might assume that its theme is already set by something higher up in the call tree. That might well be the case when you run the app, but not when directly previewing the composable.

As a result, frequently we will need our @Preview-annotated functions to set up enough of a workalike theme to be able to accurately preview the composable. Ideally, we would do this with minimal code duplication and with minimal risk of our preview theme being out of sync of the real theme.

That could be in the form of creating some kind of ThemedPreview composable that you can apply, as Google’s Sean McQuillan suggested:

@Preview
@Composable
fun MyPreview() {
   ThemedPreview { MyComposable }
}

Anton created a more elaborate system (see the Slack thread for details) that accomplishes a similar aim. In his case, he has a single “set the theme” composable that he can use both from top-level functions and from @Preview functions. And, in his case, he rigged it up to overcome the fact that @Preview does not have access to your entire Context and resources, so Anton’s theme composable uses a default-yet-reasonable theme for @Preview and uses a theme derived from a Material Design Components resource-defined theme in real code.


Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!