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