| /* |
| * 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. |
| */ |
| |
| @file:Suppress("unused") |
| package androidx.compose.runtime |
| |
| /** |
| * A CommitScope represents an object that executes some code and has a cleanup in the context of the Composition lifecycle. |
| * It has an "onDispose" operation to cleanup anything that it created whenever it leaves the composition. |
| */ |
| interface CommitScope { |
| /** |
| * Provide a lambda which will be executed as this CommitScope leaves the composition. It will be executed only once. Use this to |
| * schedule cleanup for anything that you construct during the CommitScope's creation. |
| * |
| * @param callback A callback to be executed when this CommitScope leaves the composition. |
| */ |
| fun onDispose(callback: () -> Unit) |
| } |
| |
| /** |
| * For convenience, this is just an empty lambda that we will call on CommitScopes where the user has not defined an |
| * onDispose. Saving this into a constant saves us an allocation on every initialization |
| */ |
| private val emptyDispose: () -> Unit = {} |
| private val emptyCommit: CommitScope.() -> Unit = {} |
| |
| @PublishedApi |
| internal class PreCommitScopeImpl( |
| internal val onCommit: CommitScope.() -> Unit |
| ) : CommitScope, CompositionLifecycleObserver { |
| internal var disposeCallback = emptyDispose |
| |
| override fun onDispose(callback: () -> Unit) { |
| require(disposeCallback === emptyDispose) { |
| "onDispose(...) should only be called once" |
| } |
| disposeCallback = callback |
| } |
| |
| override fun onEnter() { |
| onCommit(this) |
| } |
| |
| override fun onLeave() { |
| disposeCallback() |
| } |
| } |
| |
| @PublishedApi |
| internal class PostCommitScopeImpl( |
| internal val onCommit: CommitScope.() -> Unit, |
| private val embeddingContext: EmbeddingContext = Recomposer.current().embeddingContext |
| ) : CommitScope, CompositionLifecycleObserver, ChoreographerFrameCallback { |
| |
| private var disposeCallback = emptyDispose |
| private var hasRun = false |
| |
| override fun onDispose(callback: () -> Unit) { |
| require(disposeCallback === emptyDispose) { |
| "onDispose(...) should only be called once" |
| } |
| disposeCallback = callback |
| } |
| |
| override fun doFrame(frameTimeNanos: Long) { |
| hasRun = true |
| onCommit(this) |
| } |
| |
| override fun onEnter() { |
| embeddingContext.postFrameCallback(this) |
| } |
| |
| override fun onLeave() { |
| // If `onCommit` hasn't executed yet, we should not call `onDispose`. We should document |
| // somewhere the invariants we intend to have around call order for these. |
| if (hasRun) { |
| disposeCallback() |
| } else { |
| embeddingContext.cancelFrameCallback(this) |
| } |
| } |
| } |
| |
| /** |
| * An effect used to observe the lifecycle of the composition. The [callback] will execute once initially after the first composition |
| * is applied, and then will not fire again. The [callback] will get executed with a receiver scope that has an |
| * [onDispose][CommitScope.onDispose] method which can be used to schedule a callback to be executed once whenever the effect leaves |
| * the composition |
| * |
| * The `onActive` effect is essentially a convenience effect for `onCommit(true) { ... }`. |
| * |
| * @param callback The lambda to execute when the composition commits for the first time and becomes active. |
| * |
| * @see [onCommit] |
| * @see [onPreCommit] |
| * @see [onDispose] |
| */ |
| @Composable |
| fun onActive(callback: CommitScope.() -> Unit) { |
| remember { PostCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * An effect used to schedule work to be done when the effect leaves the composition. |
| * |
| * The `onDispose` effect is essentially a convenience effect for `onPreCommit(true) { onDispose { ... } }`. |
| * |
| * @param callback The lambda to be executed when the effect leaves the composition. |
| * |
| * @see [onCommit] |
| * @see [onPreCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| fun onDispose(callback: () -> Unit) { |
| remember { PreCommitScopeImpl(emptyCommit).also { it.disposeCallback = callback } } |
| } |
| |
| /** |
| * The onCommit effect is a lifecycle effect that will execute [callback] every time the composition commits. It is useful for |
| * executing code in lock-step with composition that has side-effects. The [callback] will get executed with a receiver scope that has an |
| * [onDispose][CommitScope.onDispose] method which can be used to schedule a callback to schedule code that cleans up the code in the |
| * callback. |
| * |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onPreCommit] |
| * @see [onActive] |
| */ |
| @Suppress("NOTHING_TO_INLINE") |
| @OptIn(ComposeCompilerApi::class) |
| @Composable |
| inline fun onCommit(noinline callback: CommitScope.() -> Unit) { |
| currentComposer.changed(PostCommitScopeImpl(callback)) |
| } |
| |
| /** |
| * The onCommit effect is a lifecycle effect that will execute [callback] every time the inputs to the effect have changed. It is useful for |
| * executing code in lock-step with composition that has side-effects that are based on the inputs. The [callback] will get executed with a |
| * receiver scope that has an [onDispose][CommitScope.onDispose] method which can be used to schedule a callback to schedule code that |
| * cleans up the code in the callback. |
| * |
| * @param v1 The input which will be compared across compositions to determine if [callback] will get executed. |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onPreCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| /*inline*/ fun </*reified*/ V1> onCommit( |
| v1: V1, |
| /*noinline*/ |
| callback: CommitScope.() -> Unit |
| ) { |
| remember(v1) { PostCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * The onCommit effect is a lifecycle effect that will execute [callback] every time the inputs to the effect have changed. It is useful for |
| * executing code in lock-step with composition that has side-effects that are based on the inputs. The [callback] will get executed with a |
| * receiver scope that has an [onDispose][CommitScope.onDispose] method which can be used to schedule a callback to schedule code that |
| * cleans up the code in the callback. |
| * |
| * @param v1 An input value which will be compared across compositions to determine if [callback] will get executed. |
| * @param v2 An input value which will be compared across compositions to determine if [callback] will get executed. |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onPreCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| /*inline*/ fun </*reified*/ V1, /*reified*/ V2> onCommit( |
| v1: V1, |
| v2: V2, |
| /*noinline*/ |
| callback: CommitScope.() -> Unit |
| ) { |
| remember(v1, v2) { PostCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * The onCommit effect is a lifecycle effect that will execute [callback] every time the inputs to the effect have changed. It is useful for |
| * executing code in lock-step with composition that has side-effects that are based on the inputs. The [callback] will get executed with a |
| * receiver scope that has an [onDispose][CommitScope.onDispose] method which can be used to schedule a callback to schedule code that |
| * cleans up the code in the callback. |
| * |
| * @param inputs A set of inputs which will be compared across compositions to determine if [callback] will get executed. |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onPreCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| fun onCommit(vararg inputs: Any?, callback: CommitScope.() -> Unit) { |
| remember(*inputs) { PostCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * The onPreCommit effect is a lifecycle effect that will execute [callback] every time the composition commits, |
| * but before those changes have been reflected on the screen. It is useful for executing code that needs to |
| * update in response to a composition and it is critical that the previous results are never seen by the user. |
| * If it is not critical, [onCommit] is recommended instead. The [callback] will get executed with a receiver scope that has an |
| * [onDispose][CommitScope.onDispose] method which can be used to schedule a callback to schedule code that cleans up the code in the |
| * callback. |
| * |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onPreCommit] |
| * @see [onActive] |
| */ |
| @Suppress("NOTHING_TO_INLINE") |
| @OptIn(ComposeCompilerApi::class) |
| @Composable |
| inline fun onPreCommit(noinline callback: CommitScope.() -> Unit) { |
| currentComposer.changed(PreCommitScopeImpl(callback)) |
| } |
| |
| /** |
| * The onPreCommit effect is a lifecycle effect that will execute [callback] every time the inputs to the |
| * effect have changed, but before those changes have been reflected on the screen. It is useful for executing |
| * code that needs to update in response to a composition and it is critical that the previous results are |
| * never seen by the user. If it is not critical, [onCommit] is recommended instead. The [callback] will get |
| * executed with a receiver scope that has an [onDispose][CommitScope.onDispose] method which can be used to |
| * schedule a callback to schedule code that cleans up the code in the callback. |
| * |
| * @param v1 The input which will be compared across compositions to determine if [callback] will get executed. |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| /*inline*/ fun </*reified*/ V1> onPreCommit( |
| v1: V1, |
| /*noinline*/ |
| callback: CommitScope.() -> Unit |
| ) { |
| remember(v1) { PreCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * The onPreCommit effect is a lifecycle effect that will execute [callback] every time the inputs to the |
| * effect have changed, but before those changes have been reflected on the screen. It is useful for executing |
| * code that needs to update in response to a composition and it is critical that the previous results are |
| * never seen by the user. If it is not critical, [onCommit] is recommended instead. The [callback] will get |
| * executed with a receiver scope that has an [onDispose][CommitScope.onDispose] method which can be used to |
| * schedule a callback to schedule code that cleans up the code in the callback. |
| * |
| * @param v1 An input value which will be compared across compositions to determine if [callback] will get executed. |
| * @param v2 An input value which will be compared across compositions to determine if [callback] will get executed. |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| /*inline*/ fun </*reified*/ V1, /*reified*/ V2> onPreCommit( |
| v1: V1, |
| v2: V2, |
| /*noinline*/ |
| callback: CommitScope.() -> Unit |
| ) { |
| remember(v1, v2) { PreCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * The onPreCommit effect is a lifecycle effect that will execute [callback] every time the inputs to the |
| * effect have changed, but before those changes have been reflected on the screen. It is useful for executing |
| * code that needs to update in response to a composition and it is critical that the previous results are |
| * never seen by the user. If it is not critical, [onCommit] is recommended instead. The [callback] will get |
| * executed with a receiver scope that has an [onDispose][CommitScope.onDispose] method which can be used to |
| * schedule a callback to schedule code that cleans up the code in the callback. |
| * |
| * @param inputs A set of inputs which will be compared across compositions to determine if [callback] will get executed. |
| * @param callback The lambda to be executed when the effect is committed to the composition. |
| * |
| * @see [onDispose] |
| * @see [onCommit] |
| * @see [onActive] |
| */ |
| @Composable |
| fun onPreCommit(vararg inputs: Any?, callback: CommitScope.() -> Unit) { |
| remember(*inputs) { PreCommitScopeImpl(callback) } |
| } |
| |
| /** |
| * The model effect is an alias to the `memo` effect, but the semantics behind how it is used are different from |
| * memoization, so we provide new named functions for the different use cases. |
| * |
| * In the case of memoization, the "inputs" of the calculation should be provided for correctness, implying if the |
| * inputs have not changed, the cached result and executing the calculation again would produce semantically identical |
| * results. |
| * |
| * In the case of "model", we are actually *intentionally* under-specifying the inputs of the calculation to cause an |
| * object to be cached across compositions. In this case, the calculation function is *not* a pure function of the inputs, |
| * and instead we are relying on the "incorrect" memoization to produce state that survives across compositions. |
| * |
| * Because these usages are so contradictory to one another, we provide a `model` alias for `memo` that is expected to |
| * be used in these cases instead of `memo`. |
| */ |
| |
| /** |
| * An Effect to get the nearest invalidation lambda to the current point of composition. This can be used to |
| * trigger an invalidation on the composition locally to cause a recompose. |
| */ |
| @Composable |
| val invalidate: () -> Unit get() { |
| val scope = currentComposer.currentRecomposeScope ?: error("no recompose scope found") |
| scope.used = true |
| return { scope.invalidate() } |
| } |
| |
| /** |
| * An Effect to construct a CompositionReference at the current point of composition. This can be used |
| * to run a separate composition in the context of the current one, preserving ambients and propagating |
| * invalidations. When this call leaves the composition, the reference is invalidated. |
| */ |
| @Composable |
| fun compositionReference(): CompositionReference { |
| return currentComposer.buildReference() |
| } |