Notes: 18. Apply the CompositionLocal Pattern to UI
The student materials have been reviewed and are updated as of September 2022.
Explore more Compose resources on our website, such as our Jetpack Compose by Tutorials book, the Jetpack Compose Tutorial For Android: Getting Started, Jetpack Compose Animations Tutorial: Getting Started, or our two live talks and podcast episodes - Building UIs In Android Using Jetpack Compose and Jetpack Compose With Denis Buketa.
Intro
[Slide 1 - LocalProviders]
The last concept you’ll learn about Jetpack Compose and state handling are LocalProviders or CompositionLocal objects. They used to be called Ambients, so if you see similar syntax, you’ll know what’s going on! :]
LocalProviders represent state that you can provide within a Compose tree, and propagate it further down the tree. This means you bind a piece of information to a state holder, and then call that holder for the information any time you need to use it.
It’s a good way to avoid having to call big and long functions to get your data, or to pass tons of data through function parameters!
Let’s see how to implement them!
Demo
Open the ReadingListDetailsActivity. Add the following composition local to the top of the class:
private val LocalReadingList = compositionLocalOf<ReadingListsWithBooks?> { error("No reading lists!") }
You create a local provider by calling compositionLocalOf(). You can either pass in a factory function for the initial value, throw an error using the error() function, or just leave the compositon local empty.
Right now, you just defined the accessor, but not the value you want to provide through it. Let’s do that next.
CompositionLocalProvider(LocalReadingList provides readingListState) {
Scaffold(
topBar = { ReadingListDetailsTopBar() },
floatingActionButton = { AddBookToReadingList(bottomDrawerState) }
) {
ReadingListDetailsModalDrawer(bottomDrawerState)
}
}
Using the CompositionLocalProvider() composable function, you wrap other content into something similar to Android’s Context. It’s an environment in which you can access any kinds of values you provide using the local providers.
And the value you provided is the readingListState. Using the syntax LocalProvider provides Value, you told Compose that within this environment, you’re going to provide the readingListState value, through the ReadingList composition local or local provider.
That way, you can also remove these two readingListState parameters from your UI tree, and access them directly. This is a common pattern in other declarative technologies, such as React or Flutter.
To access them, add the following code:
fun ReadingListDetailsTopBar() {
val readingList = LocalReadingList.current
}
fun ReadingListDetailsModalDrawer(drawerState: BottomDrawerState) {
val readingList = LocalReadingList.current
}
To get the current local value, you have to use the CompositionLocal.current property. It will fetch the value that’s stored within the composition local.
This is really good, because you now don’t have to worry about passing in many different parameters to each function. You can just use the local value! :]
–
And locals can provide any types of values, as long as you connect them through the Providers function.
Similar to how using the MaterialTheme or the LibrarianTheme functions to wrap your UI in a theme, you can automatically style the rest of the UI tree, without having to manually change all the colors.
Another CompositionLocal you’re used to using is the default Compose local, that provides the context, resources and configuration within your Compose elements!
Using them, you managed to use the stringResource() and similar functions, without specifying the context or the resources. CompositionLocals take care of that!
–
Build & run the app, and everything should work again!
[Build & Run]
Nice, everything’s still working fine! Your app is really beautiful and it works perfectly! You now understand Compose really well and you’re able to build complex and beautiful UI! :]