One Off the Slack: How Do We Implement Theme Overlays in Compose UI?

Bradleycorn asked:

I have an app that uses a Material theme in light and dark variants … pretty standard stuff. The light theme uses a white surface color and a pretty dark (navy blue) primary color. So far so good…

However, we have a few screens (formerly fragments) where the design calls for the color scheme to be inverted. That is, the dark (navy blue) color is used for the background (surface) and the content (text, textfields, etc) is white. I’m wondering what is the best way to deal with this? At first, I created a DarkSurface composable that wraps a surface and sets up some CompositionLocals to handle the color inversions. But the more I go down that path, the more cumbersome and wrong it seems. Now I’m wondering if I should create a new theme for these screens to use. Anyone else ran into something like this before and have any ideas on how to handle it?

In the classic View system, we might try accomplishing this via a theme overlay, but that’s not really a thing in Compose UI.

Google’s Nick Butcher points out the alternative: nested themes:

You can nest themes. See this sample which has a Pink theme at the root of the screen:

… but But then sets a Blue theme for a section of the UI

(note: in preparing this post, I swapped the original master links that Nick used with ones tied to the v1.0.2 tag)

PinkTheme() and BlueTheme() are defined as top-level functions in ui/theme/Theme.kt of the Owl sample project, and they are just wrappers around OwlTheme(). OwlTheme(), in turn, wraps a MaterialTheme() but also overrides the LocalElevations and LocalImages composition locals. But the colors from PinkTheme() and BlueTheme() are just passed into MaterialTheme(). MaterialTheme() sets up the design system for the composables it wraps, and you can nest MaterialTheme() calls to replace the design system for particular screens, portions of screens, etc.

Bradleycorn agreed with the plan:

Yep, that’s the path I’m going down now, thanks for the examples. It seems much more “correct” than creating a bunch of CompositionLocals and a wrapper composable to set them to provide colors. That got to be a mess fast.

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