Jetpack Compose Theming for Existing Apps (Part 2) Creating a Custom Theme

When not using Material Design Components

Yoel Gluschnaider
3 min readDec 20, 2020

In the previous post I discussed the pros and cons of two approaches for theming when introducing JC to an existing app. In this one we will deep dive into how to create a custom theme from scratch in JC. We will reference the MaterialTheme component’s source code as they recommend in the official documentation. There are a few building blocks to explain before doing this.

CompositionLocals

CompositionLocals are a way to pass down data through the composition tree implicitly, as opposed to explicitly pass them through the composable function parameter. Why do we need CompositionLocals to implement a JC theme component? to avoid the following:

Without CompositionLocals, one might need to pass the theme around even though the whole theme is not necessary as a dependency for each component. You might only need one colour from that specific theme, but a different colour in a sub component of the SomeCoolComponent e.g.:

As you see in the example above, having to pass the whole theme over an over again has two negative side effects:

  1. Breaks encapsulation: the use of a theme might be an implementation detail that the components do not wish to expose. Not to mention that if any of the sub components requires a theme property, you need to pass either the whole theme or the specific property through the tree. If you pass a specific property, any changes to a sub component that require a new property, will trigger a change in the API of all containing components.
  2. API noise: you basically pass yet another thing along in the parameters list that is required by most of your components anyway. This makes all your components very verbose.

I hope this example illustrate the need to use CompositionLocals to pass down the theme.

CompositionLocalProviders

CompositionLocalProviders are the glue that binds the CompositionLocals and provides it to the child components. They act as the scope of the CompositionLocal as the CompositionLocal will be provided to all the components passed to its content argument.

For the theme use case, we will basically use this component as a wrapper to the content composable passed to our theme composable.

Now for the Theme

Let’s implement a simple theme containing a colour scheme with 2 colours, like so:

In MaterialTheme you apply the theme by wrapping your component with the Materialtheme composable and use its properties via an object with the same name e.g.:

To understand how MaterialTheme does this we need to look at the source code. I extracted only the colours and only 2 of them for brevity, but you can expand this to other things like typography and adding more colours to the scheme.

There is quite a bit to unpack here. First of all — the MaterialTheme object that is making the getter of the colors property to be a @Composable function. This is a way to avoid using the LocalColors directly and having a single entry point to all properties of the theme. There is also a @CompsableContract annotation that provides the JC compiler plugin more information about this. I will not go into this detail, but in essence this annotation tells the compiler that this composable function just reads values from the composer so it can optimise its recomposition decisions.

The other thing to note is that we remember the colors so that whenever they change (if the LocalColors changes) we recompose the MaterialTheme and its content.

Last thing is the setting of each colour property as mutable state. This allows us to change a specific colour property and cause a recomposition of only the components that rely on that property and not the whole tree.

So to implement the same for our use case we will have the following code:

And you use it the same as you would the MaterialTheme — wrap your content with the MyTheme composable and use the colours via the MyTheme object like so MyTheme.colors.color1 .

Hope this post helps to clarify the process of creating your own theme and as always, eager to get any feedback.

In the next post I will detail the creation of a theme adapter to and existing XML based one using the same theme in this example (MyTheme with 2 colours).

--

--