CompositionLocal.kt

/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.runtime

/**
 * Compose passes data through the composition tree explicitly through means of parameters to
 * composable functions. This is often times the simplest and best way to have data flow through
 * the tree.
 *
 * Sometimes this model can be cumbersome or break down for data that is needed by lots of
 * components, or when components need to pass data between one another but keep that implementation
 * detail private. For these cases, [CompositionLocal]s can be used as an implicit way to have data
 * flow through a composition.
 *
 * [CompositionLocal]s by their nature are hierarchical. They make sense when the value of the
 * [CompositionLocal] needs to be scoped to a particular sub-hierarchy of the composition.
 *
 * One must create a [CompositionLocal] instance, which can be referenced by the consumers
 * statically. [CompositionLocal] instances themselves hold no data, and can be thought of as a
 * type-safe identifier for the data being passed down a tree. [CompositionLocal] factory functions
 * take a single parameter: a factory to create a default value in cases where a [CompositionLocal]
 * is used without a Provider. If this is a situation you would rather not handle, you can throw
 * an error in this factory.
 *
 * @sample androidx.compose.runtime.samples.createCompositionLocal
 *
 * Somewhere up the tree, a [CompositionLocalProvider] component can be used, which provides a
 * value for the [CompositionLocal]. This would often be at the "root" of a tree, but could be
 * anywhere, and can also be used in multiple places to override the provided value for a sub-tree.
 *
 * @sample androidx.compose.runtime.samples.compositionLocalProvider
 *
 * Intermediate components do not need to know about the [CompositionLocal] value, and can have zero
 * dependencies on it. For example, `SomeScreen` might look like this:
 *
 * @sample androidx.compose.runtime.samples.someScreenSample
 *
 * Finally, a component that wishes to consume the [CompositionLocal] value can use the [current]
 * property of the [CompositionLocal] key which returns the current value of the
 * [CompositionLocal], and subscribes the component to changes of it.
 *
 * @sample androidx.compose.runtime.samples.consumeCompositionLocal
 */
@Stable
sealed class CompositionLocal<T> constructor(defaultFactory: () -> T) {
    @Suppress("UNCHECKED_CAST")
    internal val defaultValueHolder = LazyValueHolder(defaultFactory)

    @Composable
    internal abstract fun provided(value: T): State<T>

    /**
     * Return the value provided by the nearest [CompositionLocalProvider] component that invokes, directly or
     * indirectly, the composable function that uses this property.
     *
     * @sample androidx.compose.runtime.samples.consumeCompositionLocal
     */
    @OptIn(InternalComposeApi::class)
    inline val current: T
        @ReadOnlyComposable
        @Composable
        get() = currentComposer.consume(this)
}

/**
 * A [ProvidableCompositionLocal] can be used in [CompositionLocalProvider] to provide values.
 *
 * @see compositionLocalOf
 * @see staticCompositionLocalOf
 * @see CompositionLocal
 * @see CompositionLocalProvider
 */
@Stable
abstract class ProvidableCompositionLocal<T> internal constructor(defaultFactory: () -> T) :
    CompositionLocal<T> (defaultFactory) {

    /**
     * Associates a [CompositionLocal] key to a value in a call to [CompositionLocalProvider].
     *
     * @see CompositionLocal
     * @see ProvidableCompositionLocal
     */
    @Suppress("UNCHECKED_CAST")
    infix fun provides(value: T) = ProvidedValue(this, value, true)

    /**
     * Associates a [CompositionLocal] key to a value in a call to [CompositionLocalProvider] if the key does not
     * already have an associated value.
     *
     * @see CompositionLocal
     * @see ProvidableCompositionLocal
     */
    @Suppress("UNCHECKED_CAST")
    infix fun providesDefault(value: T) = ProvidedValue(this, value, false)
}

/**
 * A [DynamicProvidableCompositionLocal] is a [CompositionLocal] backed by [mutableStateOf].
 * Providing new values using a [DynamicProvidableCompositionLocal] will provide the same [State]
 * with a different value. Reading the [CompositionLocal] value of a
 * [DynamicProvidableCompositionLocal] will record a read in the [RecomposeScope] of the
 * composition. Changing the provided value will invalidate the [RecomposeScope]s.
 *
 * @see compositionLocalOf
 */
internal class DynamicProvidableCompositionLocal<T> constructor(
    private val policy: SnapshotMutationPolicy<T>,
    defaultFactory: () -> T
) : ProvidableCompositionLocal<T>(defaultFactory) {

    @Composable
    override fun provided(value: T): State<T> = remember { mutableStateOf(value, policy) }.apply {
        this.value = value
    }
}

/**
 * A [StaticProvidableCompositionLocal] is a value that is expected to rarely change.
 *
 * @see staticCompositionLocalOf
 */
internal class StaticProvidableCompositionLocal<T>(defaultFactory: () -> T) :
    ProvidableCompositionLocal<T>(defaultFactory) {

    @Composable
    override fun provided(value: T): State<T> = StaticValueHolder(value)
}

/**
 * Create a [CompositionLocal] key that can be provided using [CompositionLocalProvider].
 * Changing the value provided during recomposition will invalidate the content of
 * [CompositionLocalProvider] that read the value using [CompositionLocal.current].
 *
 * [compositionLocalOf] creates a [ProvidableCompositionLocal] which can be used in a a call to
 * [CompositionLocalProvider]. Similar to [MutableList] vs. [List], if the key is made public
 * as [CompositionLocal] instead of [ProvidableCompositionLocal], it can be read using
 * [CompositionLocal.current] but not re-provided.
 *
 * @param policy a policy to determine when a [CompositionLocal] is considered changed. See
 * [SnapshotMutationPolicy] for details.
 * @param defaultFactory a value factory to supply a value when a value is not provided. This
 * factory is called when no value is provided through a [CompositionLocalProvider] of the caller
 * of the component using [CompositionLocal.current]. If no reasonable default can be provided then
 * consider throwing an exception.
 *
 * @see CompositionLocal
 * @see staticCompositionLocalOf
 * @see mutableStateOf
 */
fun <T> compositionLocalOf(
    policy: SnapshotMutationPolicy<T> =
        structuralEqualityPolicy(),
    defaultFactory: () -> T
): ProvidableCompositionLocal<T> = DynamicProvidableCompositionLocal(policy, defaultFactory)

/**
 * Create a [CompositionLocal] key that can be provided using [CompositionLocalProvider].
 *
 * Unlike [compositionLocalOf], reads of a [staticCompositionLocalOf] are not tracked by the
 * composer and changing the value provided in the [CompositionLocalProvider] call will cause the
 * entirety of the content to be recomposed instead of just the places where in the composition the
 * local value is used. This lack of tracking, however, makes a [staticCompositionLocalOf] more
 * efficient when the value provided is highly unlikely to or will never change. For example,
 * the android context, font loaders, or similar shared values, are unlikely to change for the
 * components in the content of a the [CompositionLocalProvider] and should consider using a
 * [staticCompositionLocalOf]. A color, or other theme like value, might change or even be
 * animated therefore a [compositionLocalOf] should be used.
 *
 * [staticCompositionLocalOf] creates a [ProvidableCompositionLocal] which can be used in a a
 * call to [CompositionLocalProvider]. Similar to [MutableList] vs. [List], if the key is made
 * public as [CompositionLocal] instead of [ProvidableCompositionLocal], it can be read using
 * [CompositionLocal.current] but not re-provided.
 *
 * @param defaultFactory a value factory to supply a value when a value is not provided. This
 * factory is called when no value is provided through a [CompositionLocalProvider] of the caller
 * of the component using [CompositionLocal.current]. If no reasonable default can be provided then
 * consider throwing an exception.
 *
 * @see CompositionLocal
 * @see compositionLocalOf
 */
fun <T> staticCompositionLocalOf(defaultFactory: () -> T): ProvidableCompositionLocal<T> =
    StaticProvidableCompositionLocal(defaultFactory)

/**
 * Stores [CompositionLocal]'s and their values.
 *
 * Can be obtained via [currentCompositionLocalContext] and passed to another composition
 * via [CompositionLocalProvider].
 *
 * [CompositionLocalContext] is immutable and won't be changed after its obtaining.
 */
@Stable
class CompositionLocalContext internal constructor(
    internal val compositionLocals: CompositionLocalMap
)

/**
 * [CompositionLocalProvider] binds values to [ProvidableCompositionLocal] keys. Reading the
 * [CompositionLocal] using [CompositionLocal.current] will return the value provided in
 * [CompositionLocalProvider]'s [values] parameter for all composable functions called directly
 * or indirectly in the [content] lambda.
 *
 * @sample androidx.compose.runtime.samples.compositionLocalProvider
 *
 * @see CompositionLocal
 * @see compositionLocalOf
 * @see staticCompositionLocalOf
 */
@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) {
    currentComposer.startProviders(values)
    content()
    currentComposer.endProviders()
}

/**
 * [CompositionLocalProvider] binds values to [CompositionLocal]'s, provided by [context].
 * Reading the [CompositionLocal] using [CompositionLocal.current] will return the value provided in
 * values stored inside [context] for all composable functions called directly
 * or indirectly in the [content] lambda.
 *
 * @sample androidx.compose.runtime.samples.compositionLocalProvider
 *
 * @see CompositionLocal
 * @see compositionLocalOf
 * @see staticCompositionLocalOf
 */
@Suppress("UNCHECKED_CAST")
@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(context: CompositionLocalContext, content: @Composable () -> Unit) {
    CompositionLocalProvider(
        *context.compositionLocals
            .map { it.key as ProvidableCompositionLocal<Any?> provides it.value.value }
            .toTypedArray(),
        content = content
    )
}