Photo by note thanun on Unsplash

Jetpack Compose CompositionLocal -What You Need to Know

Neil Davies
Geek Culture

--

Jetpack Compose UI is the new UI framework originally developed by the Android team in Google and is now being picked up by Jetbrains who is also developing a desktop version. If you’re a Kotlin programmer then Jetpack Compose is the UI framework that you should be seriously considering if you are looking to develop applications that have a user interface.

Jetpack Compose takes a declarative approach that uses the composition of higher-order functions. Unlike some other frameworks, it does not use classes for defining components, each composable in Compose is a higher-order function and not an instance of a class. This approach is made possible by the language features that Kotlin makes available. Key Kotlin features that Compose leverages are default arguments and a trailing lambda syntax. This allows us to write concise and idiomatic code when defining composables:

Simple Row composable containing two Text composables

In this example we don’t have to pass any parameters to the Row, we use the default values. By using the trailing lambda syntax we can define that the Row has two Text composables. Not only is this code concise, it’s also self documenting and easy to understand. It’s obvious that this Row contains just two Text composables.

Composables are functions even though their syntax may at first make them look like objects.

Here each component is a function, and we are making calls to these functions. From the syntax with the leading capital letter you may think we are constructing objects, but that is not the case. At first you may find this naming convention confusing. If they are functions, then why are they written in PascalCase — beginning with an uppercase letter, and not written in camelCase — beginning with a lowercase letter? The main reason that composables use PascalCase is that they are typically named as nouns and not verbs as a typical function might be.

So each composable is a function and this makes sense, after all we are doing function composition here, but there are a few implications to this approach.

Why we need Composition Locals

We’ve established that a composable is a higher order function. We create a composition tree by calling these functions and using the trailing lambda syntax that is a part of the Kotlin language. If we want to pass data through the composition tree we do it explicitly through means of parameter arguments.

Explicitly passing state via parameter arguments

Here you can see that we pass name information in the FirstName composable. This is often the simplest and best way to pass data through the tree, but sometimes this model can be cumbersome. When we have a complex and deep tree of composables and when the same data is needed by lots of components, passing state explicitly via function parameters can be a tedious and verbose solution. What we need in this case is a way to implicitly pass information between composables and this is where Composition Locals are needed.

Creating a CompositionLocal

Jetpack Compose comes with a number of predefined Composition Locals which are good examples of when Composition Locals should be used. But to understand how they work we are first going to look at creating our own. The example here is a little contrived and is not a great way of using Composition Locals, but it keeps things simple and allows us to explore how Composition Locals work.

In this example we are going to create a UserProfile composable which makes calls to a number of component Composables to build a UI that has a first name, middle name and last name.

A UserProfile composable that contains NameDetails that contains a number of Name composables

Here we will set an alpha value for the first, middle and last names so we can give them a different emphasis when compared to the name labels. We could create this alpha value and pass it through our intermediate NameDetails composable and then pass it to each name composable. This works fine, but we have to add an alpha parameter to NameDetails which does nothing with it and then has to pass this alpha value to each name composable. In this simple example this may not be too much of a problem, but if we had a deeper and more complex composable tree we can see that this approach could quickly become cumbersome.

To make things a little simpler we are going to create our own CompositionLocal. First we’ll create it:

Creating a CompositionLocal by calling compositionLocalOf

Here we’ve created a CompositionLocal called LocalNameAlpha and we have given it a default value of 1. We could now use this anywhere in our composable tree by getting its current value. When we call the .current function on LocalNameAlpha, it will always return the default value.

This is useful to an extent and acts very much like a global variable, but with Composition Locals we can do a little more. By wrapping the call to our NameDetails Composable with a call to CompositionLocalProvider, we can now provide an alternative value for all composables that are below NameDetails in the composable tree.

Wrapping a composable in CompositionLocalProvider to give an alternative value to the Compose sub-tree

Here we implicitly provide a LocalNameAlpha value of 0.5 for all the children of NameDetails. We also explicitly pass in the user data as a parameter to NameDetails. To make use of this in our name values we simply do the following:

Calling LocalNameAlpha.current to get the Composition Local value

In the code above we retrieve the alpha value by making a call to current. Since we provided a value when we made our call to NameDetails the value retrieved at this point in the composable tree will now be 0.5. Also note that our intermediate NameDetails composable did not have to pass this value to its child composables. Putting this all together our code looks something like this:

Simple example of creating and using a CompositionLocal

compositionLocalOf vs staticCompositionLocalOf

There’s one last thing that we should be aware of when creating our own Composition Locals. In our example when we created our own CompositionLocal we used the compositionLocalOf function, but there is an alternative method where we can call staticCompositionLocalOf. For compositionLocalOf the doc’s state :

“Creates a CompositionLocal key that can be provided using CompositionLocalProvider. Changing the value will invalidate the children of CompositionLocalProvider that read the value using CompositionLocal.current.”

And for staticCompositionLocalOf the doc’s state:

“Creates a CompositionLocal key that can be provided using CompositionLocalProvider. Changing the value provided will cause the entire tree below CompositionLocalProvider to be recomposed.”

So hopefully this is fairly clear. Changing the value provided by a static CompositionLocal will cause a recomposition of the whole UI tree that is called from within the LocalCompostionProvider block of code. Whereas changing the value of a non-static compositionLocal will only cause composables that directly call CompositionalLocal.current to recompose.

With this in mind it seems that using static Composition Locals is inefficient when their values change, as this will cause more nodes in the compose tree to recompose when compared to non-static. The docs state:

“A static CompositionLocal should only be used when the value provided is highly unlikely to change.”

At this point you might think why not just use the compositionLocal function? This seems like it will cause less of the compose tree to recompose when a CompositionLocal is changed. The answer is that non-static Composition Locals have an inherent additional cost when initially building the composable tree. For these types of Composition Locals there is an additional cost in setup, so that all the composables that read the current value can be tracked.

So there are pros and cons to using both functions to create Composition Locals. In summary, use staticCompositionLocalOf when the Composition Local is being used by a large number of composables and the value itself is unlikely to change. Where you have a value that can change, compositionLocalOf is most probably the better choice.

When to make your own Composition Locals

Our above example of creating our own Composition Local is somewhat contrived and is not the best example of when to use Composition Locals. Yet it does serve the purpose of illustrating how to create and use them. So when should you make and use your own Composition Locals?

In short, Composition Locals should be used sparingly. In our example we use them to shortcut having to pass our alpha value explicitly through the NameDetails composable. But is this the best solution here?

An alternative approach could have been to use trailing lambdas for both our NameDetails and FirstName composables. This is a great alternative as it allows us to write idiomatic code that is easy to understand while also getting rid of passing the alpha value explicitly. Since we are now using the trailing lambda syntax for our composables, this means we can make use of lexical scoping to access an alpha value declared in the parent UserProfile composable:

Using the trailing lambda syntax, we can use lexical scoping to access our alpha value

As this example shows, to simply skip passing of values in a few layers of the composable tree, there are better alternatives than using Composition Locals. You should only use Composition Locals as a last resort. In general, do not use them for passing application data (the data that is used to populate your UI with text and images). This data should always be passed explicitly.

Really, there are only a few situations where Composition Locals make sense. For example, when we have a large tree of composables where a number of these may or may not need access to a shared value, could be a good use case. Here we would not want to add additional parameters (with default values) to each composable just in case they were needed. Also, since the tree is large, it may not be possible to use solutions such as lexical scoping to share these values down the layers of the tree. Some good examples of the use of Composition Locals are the predefined ones that come with Jetpack Compose.

Predefined Composition Locals

Android comes with a number of predefined Composition Locals and there are two main areas in Compose that make heavy use of Composition Locals. These are interoperability and theming.

For interoperability, Compose needs to interact with Android framework classes like Context, Configuration and View. Rather than passing these explicitly as parameters to our composables, they are provided via predefined Composition Locals so that we can access them in any composable. Here’s an example of accessing the LocalContext Composition Local so we can Launch a new Activity:

Using the LocalContext CompositionLocal to get a Context

Another area where it is useful to have a set of predefined Composition Locals is theming. Here we again have a situation where any composable may or may not need to access state related to theming. So in this case passing these parameters implicitly is again a much better approach than passing them all explicitly via params.

In the material packages we have a number of Composition Locals for theming that we can use for varying UI values such as alpha, color, text styles etc. In our example we created our own CompositionLocal for varying alpha values but Compose comes with a predefined CompositionLocal to do this called LocalContentAlpha. As an example this is how we could change the alpha value for Text composables:

Changing the alpha value of Text composable using LocalContentAlpha

Summary

So this concludes our look at Composition Locals. They are a handy way of implicitly passing information between our composables. They allow us to reduce the number of parameters that we have to specify for each of our composable functions, and keep our code concise and easy to understand. The predefined Composition Locals that come with Jetpack Compose are good examples of how and when to use them, and since they are used widely throughout the code it makes sense that we have a good understanding of how Composition Locals work within Jetpack Compose. Being able to create our own Composition Locals is also useful, but they should be used sparingly. Composition Locals are in many ways similar to global variables, they can at times be useful but should be used sparingly and only when alternative solutions are not achievable or practicable.

--

--