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!