1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.runtime
18 
19 /**
20  * Remember the value produced by [calculation]. [calculation] will only be evaluated during the
21  * composition. Recomposition will always return the value produced by composition.
22  */
23 @Composable
24 inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
25     currentComposer.cache(false, calculation)
26 
27 /**
28  * Remember the value returned by [calculation] if [key1] compares equal (`==`) to the value it had
29  * in the previous composition, otherwise produce and remember a new value by calling [calculation].
30  */
31 @Composable
32 inline fun <T> remember(key1: Any?, crossinline calculation: @DisallowComposableCalls () -> T): T {
33     return currentComposer.cache(currentComposer.changed(key1), calculation)
34 }
35 
36 /**
37  * Remember the value returned by [calculation] if [key1] and [key2] are equal (`==`) to the values
38  * they had in the previous composition, otherwise produce and remember a new value by calling
39  * [calculation].
40  */
41 @Composable
42 inline fun <T> remember(
43     key1: Any?,
44     key2: Any?,
45     crossinline calculation: @DisallowComposableCalls () -> T
46 ): T {
47     return currentComposer.cache(
48         currentComposer.changed(key1) or currentComposer.changed(key2),
49         calculation
50     )
51 }
52 
53 /**
54  * Remember the value returned by [calculation] if [key1], [key2] and [key3] are equal (`==`) to
55  * values they had in the previous composition, otherwise produce and remember a new value by
56  * calling [calculation].
57  */
58 @Composable
59 inline fun <T> remember(
60     key1: Any?,
61     key2: Any?,
62     key3: Any?,
63     crossinline calculation: @DisallowComposableCalls () -> T
64 ): T {
65     return currentComposer.cache(
66         currentComposer.changed(key1) or
67             currentComposer.changed(key2) or
68             currentComposer.changed(key3),
69         calculation
70     )
71 }
72 
73 /**
74  * Remember the value returned by [calculation] if all values of [keys] are equal (`==`) to the
75  * values they had in the previous composition, otherwise produce and remember a new value by
76  * calling [calculation].
77  */
78 @Composable
79 inline fun <T> remember(
80     vararg keys: Any?,
81     crossinline calculation: @DisallowComposableCalls () -> T
82 ): T {
83     var invalid = false
84     for (key in keys) invalid = invalid or currentComposer.changed(key)
85     return currentComposer.cache(invalid, calculation)
86 }
87 
88 /**
89  * [key] is a utility composable that is used to "group" or "key" a block of execution inside of a
90  * composition. This is sometimes needed for correctness inside of control-flow that may cause a
91  * given composable invocation to execute more than once during composition.
92  *
93  * The value for a key *does not need to be globally unique*, and needs only be unique amongst the
94  * invocations of [key] *at that point* in composition.
95  *
96  * For instance, consider the following example:
97  *
98  * @sample androidx.compose.runtime.samples.LocallyUniqueKeys
99  *
100  * Even though there are users with the same id composed in both the top and the bottom loop,
101  * because they are different calls to [key], there is no need to create compound keys.
102  *
103  * The key must be unique for each element in the collection, however, or children and local state
104  * might be reused in unintended ways.
105  *
106  * For instance, consider the following example:
107  *
108  * @sample androidx.compose.runtime.samples.NotAlwaysUniqueKeys
109  *
110  * This example assumes that `parent.id` is a unique key for each item in the collection, but this
111  * is only true if it is fair to assume that a parent will only ever have a single child, which may
112  * not be the case. Instead, it may be more correct to do the following:
113  *
114  * @sample androidx.compose.runtime.samples.MoreCorrectUniqueKeys
115  *
116  * A compound key can be created by passing in multiple arguments:
117  *
118  * @sample androidx.compose.runtime.samples.TwoInputsKeySample
119  * @param keys The set of values to be used to create a compound key. These will be compared to
120  *   their previous values using [equals] and [hashCode]
121  * @param block The composable children for this group.
122  */
123 @Composable
124 inline fun <T> key(@Suppress("UNUSED_PARAMETER") vararg keys: Any?, block: @Composable () -> T) =
125     block()
126 
127 /**
128  * A utility function to mark a composition as supporting recycling. If the [key] changes the
129  * composition is replaced by a new composition (as would happen for [key]) but reusable nodes that
130  * are emitted by [ReusableComposeNode] are reused.
131  *
132  * @param key the value that is used to trigger recycling. If recomposed with a different value the
133  *   composer creates a new composition but tries to reuse reusable nodes.
134  * @param content the composable children that are recyclable.
135  */
136 @Composable
137 inline fun ReusableContent(key: Any?, content: @Composable () -> Unit) {
138     currentComposer.startReusableGroup(reuseKey, key)
139     content()
140     currentComposer.endReusableGroup()
141 }
142 
143 /**
144  * An optional utility function used when hosting [ReusableContent]. If [active] is false the
145  * content is treated as if it is deleted by removing all remembered objects from the composition
146  * but the node produced for the tree are not removed. When the composition later becomes active
147  * then the nodes are able to be reused inside [ReusableContent] content without requiring the
148  * remembered state of the composition's lifetime being arbitrarily extended.
149  *
150  * @param active when [active] is `true` [content] is composed normally. When [active] is `false`
151  *   then the content is deactivated and all remembered state is treated as if the content was
152  *   deleted but the nodes managed by the composition's [Applier] are unaffected. A [active] becomes
153  *   `true` any reusable nodes from the previously active composition are candidates for reuse.
154  * @param content the composable content that is managed by this composable.
155  */
156 @Composable
157 @ExplicitGroupsComposable
158 inline fun ReusableContentHost(active: Boolean, crossinline content: @Composable () -> Unit) {
159     currentComposer.startReusableGroup(reuseKey, active)
160     val activeChanged = currentComposer.changed(active)
161     if (active) {
162         content()
163     } else {
164         currentComposer.deactivateToEndGroup(activeChanged)
165     }
166     currentComposer.endReusableGroup()
167 }
168 
169 /** TODO(lmr): provide documentation */
170 val currentComposer: Composer
171     @ReadOnlyComposable
172     @Composable
173     get() {
174         throw NotImplementedError("Implemented as an intrinsic")
175     }
176 
177 /**
178  * Returns an object which can be used to invalidate the current scope at this point in composition.
179  * This object can be used to manually cause recompositions.
180  */
181 val currentRecomposeScope: RecomposeScope
182     @ReadOnlyComposable
183     @OptIn(InternalComposeApi::class)
184     @Composable
185     get() {
186         val scope = currentComposer.recomposeScope ?: error("no recompose scope found")
187         currentComposer.recordUsed(scope)
188         return scope
189     }
190 
191 /**
192  * Returns the current [CompositionLocalContext] which contains all [CompositionLocal]'s in the
193  * current composition and their values provided by [CompositionLocalProvider]'s. This context can
194  * be used to pass locals to another composition via [CompositionLocalProvider]. That is usually
195  * needed if another composition is not a subcomposition of the current one.
196  */
197 @OptIn(InternalComposeApi::class)
198 val currentCompositionLocalContext: CompositionLocalContext
199     @Composable
200     get() = CompositionLocalContext(currentComposer.buildContext().getCompositionLocalScope())
201 
202 /**
203  * This a hash value used to coordinate map externally stored state to the composition. For example,
204  * this is used by saved instance state to preserve state across activity lifetime boundaries.
205  *
206  * This value is likely to be unique but is not guaranteed unique. There are known cases, such as
207  * for loops without a [key], where the runtime does not have enough information to make the
208  * compound key hash unique.
209  *
210  * @see currentCompositeKeyHashCode
211  */
212 @Deprecated(
213     "Prefer the higher-precision currentCompositeKeyHashCode",
214     ReplaceWith("currentCompositeKeyHashCode")
215 )
216 @Suppress("DEPRECATION")
217 val currentCompositeKeyHash: Int
218     @Composable
219     @ExplicitGroupsComposable
220     @OptIn(InternalComposeApi::class)
221     get() = currentComposer.compoundKeyHash
222 
223 /**
224  * A higher-precision variation of [currentCompositeKeyHash] used to map externally stored state to
225  * the composition. By stepping up to a Long, this variation of the key hash is exponentially less
226  * likely to experience a collision.
227  *
228  * In practice, because the hash is not perfectly distributed and because there are situations where
229  * the runtime can't uniquely identify certain repeated content, collisions are still possible. This
230  * higher precision does, however, afford more confidence in the assumption that an arbitrarily
231  * sized composition hierarchy will not experience two unrelated groups having the same key hash.
232  */
233 val currentCompositeKeyHashCode: CompositeKeyHashCode
234     @Composable
235     @ExplicitGroupsComposable
236     @OptIn(InternalComposeApi::class)
237     get() = currentComposer.compositeKeyHashCode
238 
239 /**
240  * Emits a node into the composition of type [T].
241  *
242  * This function will throw a runtime exception if [E] is not a subtype of the applier of the
243  * [currentComposer].
244  *
245  * @sample androidx.compose.runtime.samples.CustomTreeComposition
246  * @param factory A function which will create a new instance of [T]. This function is NOT
247  *   guaranteed to be called in place.
248  * @param update A function to perform updates on the node. This will run every time emit is
249  *   executed. This function is called in place and will be inlined.
250  * @see Updater
251  * @see Applier
252  * @see Composition
253  */
254 // ComposeNode is a special case of readonly composable and handles creating its own groups, so
255 // it is okay to use.
256 @Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
257 @Composable
ComposeNodenull258 inline fun <T : Any, reified E : Applier<*>> ComposeNode(
259     noinline factory: () -> T,
260     update: @DisallowComposableCalls Updater<T>.() -> Unit
261 ) {
262     if (currentComposer.applier !is E) invalidApplier()
263     currentComposer.startNode()
264     if (currentComposer.inserting) {
265         currentComposer.createNode(factory)
266     } else {
267         currentComposer.useNode()
268     }
269     Updater<T>(currentComposer).update()
270     currentComposer.endNode()
271 }
272 
273 /**
274  * Emits a recyclable node into the composition of type [T].
275  *
276  * This function will throw a runtime exception if [E] is not a subtype of the applier of the
277  * [currentComposer].
278  *
279  * @sample androidx.compose.runtime.samples.CustomTreeComposition
280  * @param factory A function which will create a new instance of [T]. This function is NOT
281  *   guaranteed to be called in place.
282  * @param update A function to perform updates on the node. This will run every time emit is
283  *   executed. This function is called in place and will be inlined.
284  * @see Updater
285  * @see Applier
286  * @see Composition
287  */
288 // ComposeNode is a special case of readonly composable and handles creating its own groups, so
289 // it is okay to use.
290 @Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
291 @Composable
ReusableComposeNodenull292 inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
293     noinline factory: () -> T,
294     update: @DisallowComposableCalls Updater<T>.() -> Unit
295 ) {
296     if (currentComposer.applier !is E) invalidApplier()
297     currentComposer.startReusableNode()
298     if (currentComposer.inserting) {
299         currentComposer.createNode(factory)
300     } else {
301         currentComposer.useNode()
302     }
303     Updater<T>(currentComposer).update()
304     currentComposer.endNode()
305 }
306 
307 /**
308  * Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
309  * children of the emitted node.
310  *
311  * This function will throw a runtime exception if [E] is not a subtype of the applier of the
312  * [currentComposer].
313  *
314  * @sample androidx.compose.runtime.samples.CustomTreeComposition
315  * @param factory A function which will create a new instance of [T]. This function is NOT
316  *   guaranteed to be called in place.
317  * @param update A function to perform updates on the node. This will run every time emit is
318  *   executed. This function is called in place and will be inlined.
319  * @param content the composable content that will emit the "children" of this node.
320  * @see Updater
321  * @see Applier
322  * @see Composition
323  */
324 // ComposeNode is a special case of readonly composable and handles creating its own groups, so
325 // it is okay to use.
326 @Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE")
327 @Composable
328 inline fun <T : Any?, reified E : Applier<*>> ComposeNode(
329     noinline factory: () -> T,
330     update: @DisallowComposableCalls Updater<T>.() -> Unit,
331     content: @Composable () -> Unit
332 ) {
333     if (currentComposer.applier !is E) invalidApplier()
334     currentComposer.startNode()
335     if (currentComposer.inserting) {
336         currentComposer.createNode(factory)
337     } else {
338         currentComposer.useNode()
339     }
340     Updater<T>(currentComposer).update()
341     content()
342     currentComposer.endNode()
343 }
344 
345 /**
346  * Emits a recyclable node into the composition of type [T]. Nodes emitted inside of [content] will
347  * become children of the emitted node.
348  *
349  * This function will throw a runtime exception if [E] is not a subtype of the applier of the
350  * [currentComposer].
351  *
352  * @sample androidx.compose.runtime.samples.CustomTreeComposition
353  * @param factory A function which will create a new instance of [T]. This function is NOT
354  *   guaranteed to be called in place.
355  * @param update A function to perform updates on the node. This will run every time emit is
356  *   executed. This function is called in place and will be inlined.
357  * @param content the composable content that will emit the "children" of this node.
358  * @see Updater
359  * @see Applier
360  * @see Composition
361  */
362 // ComposeNode is a special case of readonly composable and handles creating its own groups, so
363 // it is okay to use.
364 @Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE")
365 @Composable
366 inline fun <T : Any?, reified E : Applier<*>> ReusableComposeNode(
367     noinline factory: () -> T,
368     update: @DisallowComposableCalls Updater<T>.() -> Unit,
369     content: @Composable () -> Unit
370 ) {
371     if (currentComposer.applier !is E) invalidApplier()
372     currentComposer.startReusableNode()
373     if (currentComposer.inserting) {
374         currentComposer.createNode(factory)
375     } else {
376         currentComposer.useNode()
377     }
378     Updater<T>(currentComposer).update()
379     content()
380     currentComposer.endNode()
381 }
382 
383 /**
384  * Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
385  * children of the emitted node.
386  *
387  * This function will throw a runtime exception if [E] is not a subtype of the applier of the
388  * [currentComposer].
389  *
390  * @sample androidx.compose.runtime.samples.CustomTreeComposition
391  * @param factory A function which will create a new instance of [T]. This function is NOT
392  *   guaranteed to be called in place.
393  * @param update A function to perform updates on the node. This will run every time emit is
394  *   executed. This function is called in place and will be inlined.
395  * @param skippableUpdate A function to perform updates on the node. Unlike [update], this function
396  *   is Composable and will therefore be skipped unless it has been invalidated by some other
397  *   mechanism. This can be useful to perform expensive calculations for updating the node where the
398  *   calculations are likely to have the same inputs over time, so the function's execution can be
399  *   skipped.
400  * @param content the composable content that will emit the "children" of this node.
401  * @see Updater
402  * @see SkippableUpdater
403  * @see Applier
404  * @see Composition
405  */
406 @Composable
407 @ExplicitGroupsComposable
408 inline fun <T, reified E : Applier<*>> ComposeNode(
409     noinline factory: () -> T,
410     update: @DisallowComposableCalls Updater<T>.() -> Unit,
411     noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
412     content: @Composable () -> Unit
413 ) {
414     if (currentComposer.applier !is E) invalidApplier()
415     currentComposer.startNode()
416     if (currentComposer.inserting) {
417         currentComposer.createNode(factory)
418     } else {
419         currentComposer.useNode()
420     }
421     Updater<T>(currentComposer).update()
422     SkippableUpdater<T>(currentComposer).skippableUpdate()
423     currentComposer.startReplaceableGroup(0x7ab4aae9)
424     content()
425     currentComposer.endReplaceableGroup()
426     currentComposer.endNode()
427 }
428 
429 /**
430  * Emits a recyclable node into the composition of type [T]. Nodes emitted inside of [content] will
431  * become children of the emitted node.
432  *
433  * This function will throw a runtime exception if [E] is not a subtype of the applier of the
434  * [currentComposer].
435  *
436  * @sample androidx.compose.runtime.samples.CustomTreeComposition
437  * @param factory A function which will create a new instance of [T]. This function is NOT
438  *   guaranteed to be called in place.
439  * @param update A function to perform updates on the node. This will run every time emit is
440  *   executed. This function is called in place and will be inlined.
441  * @param skippableUpdate A function to perform updates on the node. Unlike [update], this function
442  *   is Composable and will therefore be skipped unless it has been invalidated by some other
443  *   mechanism. This can be useful to perform expensive calculations for updating the node where the
444  *   calculations are likely to have the same inputs over time, so the function's execution can be
445  *   skipped.
446  * @param content the composable content that will emit the "children" of this node.
447  * @see Updater
448  * @see SkippableUpdater
449  * @see Applier
450  * @see Composition
451  */
452 @Composable
453 @ExplicitGroupsComposable
454 inline fun <T, reified E : Applier<*>> ReusableComposeNode(
455     noinline factory: () -> T,
456     update: @DisallowComposableCalls Updater<T>.() -> Unit,
457     noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
458     content: @Composable () -> Unit
459 ) {
460     if (currentComposer.applier !is E) invalidApplier()
461     currentComposer.startReusableNode()
462     if (currentComposer.inserting) {
463         currentComposer.createNode(factory)
464     } else {
465         currentComposer.useNode()
466     }
467     Updater<T>(currentComposer).update()
468     SkippableUpdater<T>(currentComposer).skippableUpdate()
469     currentComposer.startReplaceableGroup(0x7ab4aae9)
470     content()
471     currentComposer.endReplaceableGroup()
472     currentComposer.endNode()
473 }
474 
invalidAppliernull475 @PublishedApi internal fun invalidApplier(): Unit = error("Invalid applier")
476 
477 /**
478  * An Effect to construct a [CompositionContext] at the current point of composition. This can be
479  * used to run a separate composition in the context of the current one, preserving
480  * [CompositionLocal]s and propagating invalidations. When this call leaves the composition, the
481  * context is invalidated.
482  */
483 @OptIn(InternalComposeApi::class)
484 @Composable
485 fun rememberCompositionContext(): CompositionContext {
486     return currentComposer.buildContext()
487 }
488