blob: 8e37504d80f0c3803264760274c1ad3f78f4b35f [file] [log] [blame]
/*
* 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
/**
* 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, [Ambient]s can be used as an implicit way to have data flow
* through a composition.
*
* [Ambient]s by their nature are hierarchical. They make sense when the value of the ambient needs
* to be scoped to a particular sub-hierarchy of the composition.
*
* ```
* // One must create an Ambient instance, which can be referenced by the consumers
* // statically. Ambient instances themselves hold no data, and can be thought of as a
* // type-safe identifier for the data being passed down a tree. The Ambient constructor takes a
* // single parameter, a factory to create a default value in cases where an ambient is
* // used without a Provider. If this is a situation you would rather not handle, you
* // can throw an error in this factory
* val ActiveUser = Ambient.of<User> { error("No active user found!") }
*
* // Somewhere up the tree, a "Provider" component can be used, which has a required
* // "value" parameter. 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.
* @Composable fun App(user: User) {
* ActiveUser.Provider(value=user) {
* SomeScreen()
* }
* }
*
* // intermediate components do not need to know about the ambient value, and can have
* // zero dependencies on it
* @Composable fun SomeScreen() {
* UserPhoto()
* }
*
* // A component that wishes to consume the ambient value can use the corresponding
* // "ambient" effect, which returns the current value of the ambient, and subscribes the component
* // to changes of it
* @Composable fun UserPhoto() {
* val user = +ambient(ActiveUser)
* ImageView(src=user.profilePhotoUrl)
* }
* ```
*
* @see [ambient]
*/
class Ambient<T>
@PublishedApi
internal constructor(private val key: String, private val defaultFactory: (() -> T)? = null) {
@Suppress("UNCHECKED_CAST")
internal val defaultValue by lazy {
val fn = defaultFactory
if (fn != null) fn()
else null as T
}
companion object {
/**
* Creates an ambient to be used during composition.
*
* @param key A string identifier used to disambiguate the Ambient.
* Defaults to the name of [T].
* @param defaultFactory A lambda to run to create a default value of this ambient for when
* the ambient is consumed in a composition and there is no provider above the point of
* consumption. If no factory is provided, and [T] is not a nullable type, an exception
* will be thrown. This factory will not be executed more than once.
*/
inline fun <reified T> of(
key: String = T::class.java.simpleName,
noinline defaultFactory: (() -> T)? = null
) = Ambient(key, defaultFactory)
}
override fun hashCode() = key.hashCode()
override fun equals(other: Any?) = this === other
override fun toString(): String = "Ambient<$key>"
internal class Holder<T>(val ambient: Ambient<T>, var value: T)
/**
* The Provider component allows one to provide an ambient value to a section of the tree during
* composition. All consumers of the ambient that are underneath this component will receive the
* value provided here. Consuming components are guaranteed to be invalidated and recomposed
* when this value changes.
*
* @param value The current value of the ambient.
* @param children Everything composed inside this block will get [value] when consuming this
* ambient
*
* @see ambient
*/
@Composable
fun Provider(
value: T, @Children
children: @Composable() () -> Unit
) {
with(currentComposerNonNull) {
val holder = +memo {
Holder(
this@Ambient,
value
)
}
holder.value = value
startProvider(holder, value)
@Suppress("PLUGIN_ERROR")
children()
endProvider()
}
}
}