For the More Curious: Creating Your Own CompositionLocals

In this chapter, you learned about several built-in CompositionLocals and used the LocalContext to obtain a Context in your composable. If you want, you can also define your own CompositionLocals.

Declaring a CompositionLocal is particularly useful when you want to give your composables access to new values without introducing an additional parameter. This works best when the information being provided applies to many composables and can be shared across an entire section of your composition hierarchy. Much like global variables, CompositionLocals can be dangerous if used haphazardly. If a value should only be available to one composable, we recommend sticking to parameters.

Suppose your application needed to track analytics to see what features your users rely on most. You could create a class called AnalyticsManager to implement the analytics logging yourself. But many composables would likely need to report analytics, and you do not want to concern yourself with passing instances of AnalyticsManager through layer after layer of composables. That is where CompositionLocals come in.

Making a CompositionLocal is a two-step process. First, you need to define the CompositionLocal. Second, you need to set the value of the CompositionLocal in your UI hierarchy.

CompositionLocals are defined by creating a public, file-level property of type CompositionLocal – like, say, a LocalAnalyticsManager. This value basically acts as a key to get hold of the corresponding value. You can assign a value for this property using the compositionLocalOf function.

This function takes in a lambda to provide a default value for the CompositionLocal. For many CompositionLocals, including your hypothetical LocalAnalyticsManager, there is no default value – a value must always be explicitly set in the composition itself. In these cases, you can simply throw an exception indicating that the CompositionLocal was read before it was set.

    val LocalAnalyticsManager = compositionLocalOf<AnalyticsManager> {
        error("AnalyticsManager not set")
    }

With the CompositionLocal defined, you can then specify its value at runtime. You do this using the CompositionLocalProvider composable. CompositionLocalProvider takes in a set of all the CompositionLocals you want to specify, along with a value for each one. When a component requests one of the CompositionLocals in the provider, the value you specify will be returned.

    @Composable
    fun PizzaBuilderScreen(
        analyticsManager: AnalyticsManager,
        modifier: Modifier = Modifier
    ) {
        CompositionLocalProvider(
            LocalAnalyticsManager provides analyticsManager
        ) {
            Scaffold(
                modifier = modifier,
                ...
            )
        }
    }

You may be asking, OK, but how does PizzaBuilderScreen obtain its analyticsManager? This question has several answers, and in your own code, you will have to decide for yourself how to answer this question. If AnalyticsManager is easy to create, you may be able to instantiate it directly inside PizzaBuilderScreen. (If you do this, make sure to remember it!)

Alternatively, you could create this value elsewhere in your application – possibly as a singleton – and pass it through your composition hierarchy as an argument. Either approach is valid, and it is up to you and your team to decide how dependencies like analyticsManager should make their way through your code.

With the CompositionLocal and its provider in place, LocalAnalyticsManager is ready to be used. To obtain an AnalyticsManager, you call LocalAnalyticsManager.current inside a composable function. The Compose runtime will look at your composition hierarchy to find an appropriate provider for this value. Once a provider is found, the value that was set will be returned by the CompositionLocal.

If several providers are found, the closest parent in the hierarchy will be chosen and its value will be used. If no provider is found, the default value of the CompositionLocal (specified with the compositionLocalOf) will be provided.

Storing values this way allows for easy access throughout your composition hierarchy, but we recommend using CompositionLocals sparingly. They are great for accessing more general or widely used dependencies. But you can find yourself getting into trouble if you hold your application state in a CompositionLocal, as this makes it difficult to track down exactly where a value is coming from.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.74.25