1 /*
<lambda>null2  * Copyright 2020 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 import androidx.compose.runtime.mock.InlineLinear
20 import androidx.compose.runtime.mock.Linear
21 import androidx.compose.runtime.mock.MockViewValidator
22 import androidx.compose.runtime.mock.View
23 import androidx.compose.runtime.mock.ViewApplier
24 import androidx.compose.runtime.mock.compositionTest
25 import androidx.compose.runtime.mock.expectChanges
26 import androidx.compose.runtime.mock.revalidate
27 import androidx.compose.runtime.mock.validate
28 import androidx.compose.runtime.mock.view
29 import androidx.compose.runtime.snapshots.Snapshot
30 import kotlin.random.Random
31 import kotlin.test.Test
32 import kotlin.test.assertEquals
33 import kotlin.test.assertFalse
34 import kotlin.test.assertNotEquals
35 import kotlin.test.assertSame
36 import kotlin.test.assertTrue
37 
38 @Stable
39 class MovableContentTests {
40 
41     @Test
42     fun testMovableContentSharesState() = compositionTest {
43         var lastPrivateState: State<Int> = mutableStateOf(0)
44         var portrait by mutableStateOf(false)
45 
46         val content = movableContentOf {
47             val privateState = remember { mutableStateOf(0) }
48             lastPrivateState = privateState
49             Text("Some text")
50             Text("Some other text")
51         }
52 
53         @Composable
54         fun Test() {
55             if (portrait) {
56                 Column { content() }
57             } else {
58                 Row { content() }
59             }
60         }
61 
62         compose { Test() }
63 
64         validate {
65             fun MockViewValidator.value() {
66                 Text("Some text")
67                 Text("Some other text")
68             }
69 
70             if (portrait) {
71                 Column { this.value() }
72             } else {
73                 Row { this.value() }
74             }
75         }
76 
77         val firstPrivateState = lastPrivateState
78         portrait = true
79         Snapshot.sendApplyNotifications()
80 
81         expectChanges()
82         revalidate()
83 
84         assertSame(firstPrivateState, lastPrivateState, "The state should be shared")
85     }
86 
87     @Test
88     fun movableContentPreservesNodes() = compositionTest {
89         var portrait by mutableStateOf(false)
90 
91         val content = movableContentOf {
92             Text("Some text")
93             Text("Some other text")
94         }
95 
96         @Composable
97         fun Test() {
98             if (portrait) {
99                 Column { content() }
100             } else {
101                 Row { content() }
102             }
103         }
104 
105         compose { Test() }
106 
107         fun MockViewValidator.value() {
108             Text("Some text")
109             Text("Some other text")
110         }
111 
112         validate {
113             if (portrait) {
114                 Column { this.value() }
115             } else {
116                 Row { this.value() }
117             }
118         }
119 
120         val firstText = root.findFirst { it.name == "Text" }
121         portrait = true
122 
123         expectChanges()
124         revalidate()
125 
126         // Nodes should be shared
127         val newFirstText = root.findFirst { it.name == "Text" }
128         assertSame(firstText, newFirstText, "Text instance should be identical")
129     }
130 
131     @Test
132     fun movingContent_mainComposer() = compositionTest {
133         val rememberedObject = mutableListOf<RememberedObject>()
134 
135         @Composable
136         fun addRememberedObject() {
137             remember { RememberedObject().also { rememberedObject.add(it) } }
138         }
139 
140         val content = movableContentOf {
141             Row {
142                 addRememberedObject()
143                 Text("Some text")
144                 Marker()
145             }
146         }
147 
148         fun MockViewValidator.validateContent() {
149             Row {
150                 Text("Some text")
151                 Marker()
152             }
153         }
154 
155         var first by mutableStateOf(true)
156 
157         compose {
158             Row {
159                 if (first) content()
160                 Text("Some other text")
161             }
162             Row {
163                 Text("Some more text")
164                 if (!first) content()
165             }
166         }
167 
168         val marker: View = root.findFirst { it.name == "Marker" }
169 
170         fun validate() {
171             validate {
172                 Row {
173                     if (first) validateContent()
174                     Text("Some other text")
175                 }
176                 Row {
177                     Text("Some more text")
178                     if (!first) validateContent()
179                 }
180             }
181 
182             assertEquals(
183                 expected = marker,
184                 actual = root.findFirst { it.name == "Marker" },
185                 message = "Expected marker node to move with the movable content"
186             )
187             assertTrue("Expected all remember observers to be kept alive") {
188                 rememberedObject.all { it.isLive }
189             }
190         }
191 
192         validate()
193 
194         first = false
195         expectChanges()
196         validate()
197 
198         first = true
199         expectChanges()
200         validate()
201     }
202 
203     @Test
204     fun moveContent_subcompose() = compositionTest {
205         val rememberObservers = mutableListOf<RememberedObject>()
206 
207         @Composable
208         fun addRememberObject() {
209             remember { RememberedObject().also { rememberObservers.add(it) } }
210         }
211 
212         val content = movableContentOf {
213             Row {
214                 addRememberObject()
215                 Text("Text from value")
216                 Marker()
217             }
218         }
219 
220         fun MockViewValidator.validateContent() {
221             Row {
222                 Text("Text from value")
223                 Marker()
224             }
225         }
226 
227         val inMain = 0
228         val inSubcompose1 = 1
229         val inSubcompose2 = 2
230 
231         var position by mutableStateOf(inMain)
232 
233         compose {
234             Row {
235                 if (position == inMain) content()
236                 Subcompose {
237                     Row {
238                         if (position == inSubcompose1) content()
239                         Text("Some other text")
240                     }
241                 }
242                 Subcompose {
243                     Row {
244                         Text("Some more text")
245                         if (position == inSubcompose2) content()
246                     }
247                 }
248             }
249         }
250 
251         val marker: View = root.findFirst { it.name == "Marker" }
252 
253         fun validate() {
254             validate {
255                 Row {
256                     if (position == inMain) validateContent()
257                     Subcompose {
258                         Row {
259                             if (position == inSubcompose1) validateContent()
260                             Text("Some other text")
261                         }
262                     }
263                     Subcompose {
264                         Row {
265                             Text("Some more text")
266                             if (position == inSubcompose2) validateContent()
267                         }
268                     }
269                 }
270             }
271 
272             assertEquals(
273                 expected = marker,
274                 actual = root.findFirst { it.name == "Marker" },
275                 message = "Expected marker node to move with the movable content"
276             )
277             assertTrue("Expected all remember observers to be kept alive") {
278                 rememberObservers.all { it.isLive }
279             }
280         }
281 
282         validate()
283 
284         for (newPosition in
285             listOf(inSubcompose1, inSubcompose2, inSubcompose1, inMain, inSubcompose2, inMain)) {
286             position = newPosition
287             expectChanges()
288             validate()
289         }
290     }
291 
292     @Test
293     fun normalMoveWithContentMove() = compositionTest {
294         val random = Random(1337)
295         val list = mutableStateListOf(*List(10) { it }.toTypedArray())
296 
297         val content = movableContentOf { Marker() }
298         var position by mutableStateOf(-1)
299 
300         compose {
301             Column {
302                 if (position == -1) content()
303                 for (item in list) {
304                     key(item) {
305                         Text("Item $item")
306                         if (item == position) content()
307                     }
308                 }
309             }
310         }
311 
312         val marker: View = root.findFirst { it.name == "Marker" }
313 
314         fun validate() {
315             validate {
316                 Column {
317                     if (position == -1) Marker()
318                     for (item in list) {
319                         Text("Item $item")
320                         if (item == position) Marker()
321                     }
322                 }
323             }
324 
325             assertEquals(
326                 expected = marker,
327                 actual = root.findFirst { it.name == "Marker" },
328                 message = "Expected marker node to move with the movable content"
329             )
330         }
331 
332         validate()
333 
334         repeat(10) {
335             position = it
336             list.shuffle(random)
337             expectChanges()
338             validate()
339         }
340 
341         position = -1
342         list.shuffle(random)
343         expectChanges()
344         validate()
345     }
346 
347     @Test
348     fun removeAndInsertWithMoveAway() = compositionTest {
349         var position by mutableStateOf(0)
350         var skipItem by mutableStateOf(5)
351 
352         val content = movableContentOf { Marker() }
353         compose {
354             Row {
355                 if (position == -1) content()
356                 Column {
357                     repeat(10) { item ->
358                         key(item) {
359                             if (skipItem != item) Text("Item $item")
360                             if (position == item) content()
361                         }
362                     }
363                 }
364             }
365         }
366 
367         val marker: View = root.findFirst { it.name == "Marker" }
368 
369         fun validate() {
370             validate {
371                 Row {
372                     if (position == -1) Marker()
373                     Column {
374                         repeat(10) { item ->
375                             if (skipItem != item) Text("Item $item")
376                             if (position == item) Marker()
377                         }
378                     }
379                 }
380             }
381             assertEquals(
382                 expected = marker,
383                 actual = root.findFirst { it.name == "Marker" },
384                 message = "Expected marker node to move with the movable content"
385             )
386         }
387 
388         validate()
389 
390         repeat(10) { markerPosition ->
391             repeat(10) { skip ->
392                 position = -1
393                 skipItem = -1
394                 expectChanges()
395                 validate()
396 
397                 // Move the marker and delete an item.
398                 position = markerPosition
399                 skipItem = skip
400                 expectChanges()
401                 validate()
402 
403                 // Move the marker away and insert an item
404                 position = -1
405                 skipItem = -1
406                 expectChanges()
407 
408                 // Move the marker back
409                 position = markerPosition
410                 expectChanges()
411                 validate()
412 
413                 // Move the marker way and delete an item
414                 position = -1
415                 skipItem = skip
416                 expectChanges()
417                 validate()
418             }
419         }
420     }
421 
422     @Test
423     fun invalidationsMoveWithContent() = compositionTest {
424         var data by mutableStateOf(0)
425         var position by mutableStateOf(-1)
426         val content = movableContentOf { Text("data = $data") }
427 
428         compose {
429             Row {
430                 if (position == -1) content()
431                 repeat(10) { item ->
432                     key(item) {
433                         Text("Item $item")
434                         if (position == item) content()
435                     }
436                 }
437             }
438         }
439 
440         validate {
441             fun MockViewValidator.content() {
442                 Text("data = $data")
443             }
444             Row {
445                 if (position == -1) this.content()
446                 repeat(10) { item ->
447                     Text("Item $item")
448                     if (position == item) this.content()
449                 }
450             }
451         }
452 
453         repeat(10) { newData ->
454             data = newData
455             position = newData
456             expectChanges()
457             revalidate()
458         }
459     }
460 
461     @Test
462     fun projectedBinaryTree() = compositionTest {
463         class Node(value: Int, left: Node? = null, right: Node? = null) {
464             var value by mutableStateOf(value)
465             var left by mutableStateOf(left)
466             var right by mutableStateOf(right)
467 
468             fun validateNode(validator: MockViewValidator) {
469                 with(validator) { Marker(value) }
470                 left?.validateNode(validator)
471                 right?.validateNode(validator)
472             }
473 
474             fun forEach(block: (node: Node) -> Unit) {
475                 block(this)
476                 left?.forEach(block)
477                 right?.forEach(block)
478             }
479 
480             fun swap() {
481                 val oldLeft = left
482                 val oldRight = right
483                 left = oldRight
484                 right = oldLeft
485             }
486 
487             override fun toString(): String = "$value($left, $right)"
488         }
489 
490         fun buildTree(level: Int): Node {
491             var index = 0
492             fun build(level: Int): Node =
493                 if (level > 1) Node(index++, build(level - 1), build(level - 1)) else Node(index++)
494             return build(level)
495         }
496 
497         val tree = buildTree(6)
498 
499         val contents = mutableMapOf<Node?, @Composable () -> Unit>()
500         tree.forEach { node ->
501             contents[node] = movableContentOf {
502                 Marker(node.value)
503                 contents[node.left]?.invoke()
504                 contents[node.right]?.invoke()
505             }
506         }
507 
508         compose { contents[tree]?.invoke() }
509 
510         validate { tree.validateNode(this) }
511 
512         tree.forEach { it.swap() }
513 
514         expectChanges()
515 
516         revalidate()
517 
518         tree.forEach { it.swap() }
519 
520         expectChanges()
521 
522         revalidate()
523     }
524 
525     @Test
526     fun multipleContentsMovingIntoCommonParent() = compositionTest {
527         val content1 = movableContentOf {
528             Text("1-1")
529             Text("1-2")
530             Text("1-3")
531         }
532         val content2 = movableContentOf {
533             Text("2-4")
534             Text("2-5")
535             Text("2-6")
536         }
537         val content3 = movableContentOf {
538             Text("3-7")
539             Text("3-8")
540             Text("3-9")
541         }
542 
543         var case by mutableStateOf(0)
544         var level by mutableStateOf(0)
545 
546         @Composable
547         fun sep() {
548             Text("-----")
549         }
550 
551         @Composable
552         fun cases() {
553             when (case) {
554                 0 -> {
555                     sep()
556                     content1()
557                     sep()
558                     content2()
559                     sep()
560                     content3()
561                     sep()
562                 }
563                 1 -> {
564                     content2()
565                     sep()
566                     content3()
567                     sep()
568                     content1()
569                 }
570                 2 -> {
571                     sep()
572                     content3()
573                     content1()
574                     content2()
575                     sep()
576                 }
577             }
578         }
579 
580         compose {
581             Column {
582                 if (level == 0) {
583                     cases()
584                 }
585                 Column {
586                     if (level == 1) {
587                         cases()
588                     }
589                 }
590             }
591         }
592 
593         validate {
594             fun MockViewValidator.sep() {
595                 Text("-----")
596             }
597 
598             fun MockViewValidator.value1() {
599                 Text("1-1")
600                 Text("1-2")
601                 Text("1-3")
602             }
603 
604             fun MockViewValidator.value2() {
605                 Text("2-4")
606                 Text("2-5")
607                 Text("2-6")
608             }
609 
610             fun MockViewValidator.value3() {
611                 Text("3-7")
612                 Text("3-8")
613                 Text("3-9")
614             }
615 
616             fun MockViewValidator.cases() {
617                 when (case) {
618                     0 -> {
619                         this.sep()
620                         this.value1()
621                         this.sep()
622                         this.value2()
623                         this.sep()
624                         this.value3()
625                         this.sep()
626                     }
627                     1 -> {
628                         this.value2()
629                         this.sep()
630                         this.value3()
631                         this.sep()
632                         this.value1()
633                     }
634                     2 -> {
635                         this.sep()
636                         this.value3()
637                         this.value1()
638                         this.value2()
639                         this.sep()
640                     }
641                 }
642             }
643 
644             Column {
645                 if (level == 0) {
646                     this.cases()
647                 }
648                 Column {
649                     if (level == 1) {
650                         this.cases()
651                     }
652                 }
653             }
654         }
655 
656         fun textMap(): Map<String?, View> {
657             val result = mutableMapOf<String?, View>()
658             fun collect(view: View) {
659                 if (view.name == "Text") {
660                     if (view.text?.contains('-') == false) result[view.text] = view
661                 }
662                 for (child in view.children) {
663                     collect(child)
664                 }
665             }
666             collect(root)
667             return result
668         }
669 
670         val initialMap = textMap()
671 
672         fun validateInstances() {
673             val currentMap = textMap()
674             for (entry in currentMap) {
675                 if (initialMap[entry.key] !== entry.value) {
676                     error("The text value ${entry.key} had a different instance created")
677                 }
678             }
679         }
680 
681         fun test(l: Int, c: Int) {
682             case = c
683             level = l
684             advance(ignorePendingWork = true)
685             revalidate()
686             validateInstances()
687         }
688 
689         test(0, 0)
690         test(1, 1)
691         test(0, 2)
692         test(1, 0)
693         test(0, 1)
694         test(1, 2)
695     }
696 
697     @Test
698     fun childIndexesAreCorrectlyCalculated() = compositionTest {
699         val content = movableContentOf { Marker(0) }
700 
701         var vertical by mutableStateOf(false)
702         compose {
703             if (vertical) {
704                 Row {
705                     Empty()
706                     content()
707                 }
708             } else {
709                 Column {
710                     Empty()
711                     content()
712                 }
713             }
714         }
715 
716         validate {
717             if (vertical) {
718                 Row { Marker(0) }
719             } else {
720                 Column { Marker(0) }
721             }
722         }
723 
724         vertical = true
725         expectChanges()
726         revalidate()
727     }
728 
729     @Test
730     fun validateRecomposeScopesDoNotGetLost() = compositionTest {
731         var isHorizontal by mutableStateOf(false)
732         val displayValue = mutableStateOf(0)
733         val content = movableContentOf { DisplayInt(displayValue) }
734 
735         compose { Stack(isHorizontal) { Row { content() } } }
736 
737         validate { Stack(isHorizontal) { Row { DisplayInt(displayValue) } } }
738 
739         displayValue.value++
740         expectChanges()
741         revalidate()
742 
743         isHorizontal = true
744         Snapshot.sendApplyNotifications()
745         advanceTimeBy(10)
746 
747         displayValue.value++
748         expectChanges()
749         revalidate()
750     }
751 
752     @Test
753     fun compositionLocalsShouldBeAvailable() = compositionTest {
754         var someValue by mutableStateOf(0)
755         val local = staticCompositionLocalOf<Int> { error("No value provided for local") }
756 
757         compose {
758             Wrap(20) {
759                 CompositionLocalProvider(local provides 10) {
760                     // Remember is missing intentionally so it creates a new value to ensure the
761                     // new values see the  correct provider scope.
762                     val content = movableContentOf {
763                         Text("Local = ${local.current}")
764                         Text("SomeValue = $someValue")
765                     }
766                     if (someValue % 2 == 0) content() else content()
767                 }
768             }
769         }
770 
771         validate {
772             Text("Local = 10")
773             Text("SomeValue = $someValue")
774         }
775 
776         someValue++
777         advance()
778 
779         revalidate()
780     }
781 
782     @Test
783     fun compositionLocalsShouldBeAvailableInNestedContent() = compositionTest {
784         var someValue by mutableStateOf(0)
785         val local = staticCompositionLocalOf<Int> { error("No value provided for local") }
786 
787         val parent = movableContentOf<@Composable () -> Unit> { child -> Wrap { child() } }
788 
789         val child = movableContentOf {
790             Text("Local = ${local.current}")
791             Text("SomeValue = $someValue")
792         }
793 
794         compose {
795             Wrap {
796                 CompositionLocalProvider(local provides 10) {
797                     // Remember is missing intentionally so it creates a new value to ensure the
798                     // new values see the  correct provider scope.
799                     if (someValue % 2 == 0)
800                         parent {
801                             Wrap {
802                                 Text("One")
803                                 child()
804                             }
805                         }
806                     else
807                         parent {
808                             child()
809                             Text("Two")
810                         }
811                 }
812             }
813         }
814 
815         validate {
816             if (someValue % 2 == 0) {
817                 Text("One")
818                 Text("Local = 10")
819                 Text("SomeValue = $someValue")
820             } else {
821                 Text("Local = 10")
822                 Text("SomeValue = $someValue")
823                 Text("Two")
824             }
825         }
826 
827         someValue++
828         advance()
829 
830         revalidate()
831     }
832 
833     @Test
834     fun subcomposeLifetime_no_movable_content() = compositionTest {
835         val rememberObject = RememberedObject()
836         var useInMain by mutableStateOf(false)
837         var useInSub1 by mutableStateOf(false)
838         var useInSub2 by mutableStateOf(false)
839 
840         @Composable
841         fun use() {
842             remember { rememberObject }
843         }
844         compose {
845             if (useInMain) use()
846             Subcompose { if (useInSub1) use() }
847             Subcompose { if (useInSub2) use() }
848         }
849 
850         fun expectUnused() {
851             advance()
852             assertFalse(rememberObject.isLive, "RememberObject unexpectedly used")
853         }
854         fun expectUsed() {
855             advance()
856             assertTrue(rememberObject.isLive, "Expected RememberObject to be used")
857         }
858 
859         expectUnused()
860 
861         // Add a use in main
862         useInMain = true
863         expectUsed()
864 
865         // Add in sub-composes
866         useInSub1 = true
867         useInSub2 = true
868         expectUsed()
869 
870         // Remove it from main
871         useInMain = false
872         expectUsed()
873 
874         // Remove it from sub1
875         useInSub1 = false
876         expectUsed()
877 
878         // Transfer it from sub1 to sub2
879         useInSub1 = false
880         useInSub2 = true
881         expectUsed()
882 
883         // Remove it altogether
884         useInMain = false
885         useInSub1 = false
886         useInSub2 = false
887         expectUnused()
888     }
889 
890     @Test
891     fun subcomposeLifetime_with_movable_content() = compositionTest {
892         val rememberObject = RememberedObject()
893         var useInMain by mutableStateOf(false)
894         var useInSub1 by mutableStateOf(false)
895         var useInSub2 by mutableStateOf(false)
896 
897         @Suppress("UNUSED_VARIABLE") // Object not remembered without
898         val rememberTheObject = movableContentOf {
899             val obj = remember { rememberObject }
900         }
901 
902         @Composable
903         fun use() {
904             rememberTheObject()
905         }
906         compose {
907             if (useInMain) use()
908             Subcompose { if (useInSub1) use() }
909             Subcompose { if (useInSub2) use() }
910         }
911 
912         fun expectUnused() {
913             advance()
914             assertFalse(rememberObject.isLive, "RememberObject unexpectedly used")
915         }
916         fun expectUsed() {
917             advance()
918             assertTrue(rememberObject.isLive, "Expected RememberObject to be used")
919         }
920 
921         expectUnused()
922 
923         // Add a use in main
924         useInMain = true
925         expectUsed()
926 
927         // Add in sub-composes
928         useInSub1 = true
929         useInSub2 = true
930         expectUsed()
931 
932         // Remove it from main
933         useInMain = false
934         expectUsed()
935 
936         // Remove it from sub1
937         useInSub1 = false
938         expectUsed()
939 
940         // Transfer it from sub1 to sub2
941         useInSub1 = false
942         useInSub2 = true
943         expectUsed()
944 
945         // Remove it altogether
946         useInMain = false
947         useInSub1 = false
948         useInSub2 = false
949         expectUnused()
950     }
951 
952     @Test // Regression test for 230830644 and 235398298
953     fun deferredSubcompose_conditional_rootLevelChildren() = compositionTest {
954         var subcompose by mutableStateOf(false)
955         var lastPrivateState: State<Int> = mutableStateOf(0)
956 
957         val content = movableContentOf {
958             lastPrivateState = remember { mutableStateOf(0) }
959             Text("Movable content")
960         }
961 
962         compose {
963             Text("Main content start")
964             if (!subcompose) {
965                 content()
966             }
967             Text("Main content end")
968             if (subcompose) {
969                 DeferredSubcompose {
970                     Text("Sub-composed content start")
971                     content()
972                     Text("Sub-composed content end")
973                 }
974             }
975         }
976 
977         validate {
978             Text("Main content start")
979             if (!subcompose) {
980                 Text("Movable content")
981             }
982             Text("Main content end")
983             if (subcompose) {
984                 DeferredSubcompose {
985                     Text("Sub-composed content start")
986                     Text("Movable content")
987                     Text("Sub-composed content end")
988                 }
989             }
990         }
991 
992         val expectedState = lastPrivateState
993         subcompose = true
994         expectChanges()
995         revalidate()
996 
997         assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
998 
999         subcompose = false
1000         expectChanges()
1001         revalidate()
1002 
1003         assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
1004     }
1005 
1006     @Test // Regression test for 230830644 and 235398298
1007     fun deferredSubcompose_conditional_nestedChildren() = compositionTest {
1008         var subcompose by mutableStateOf(false)
1009         var lastPrivateState: State<Int> = mutableStateOf(0)
1010 
1011         val content = movableContentOf {
1012             lastPrivateState = remember { mutableStateOf(0) }
1013             Text("Movable content")
1014         }
1015 
1016         compose {
1017             Text("Main content start")
1018             if (!subcompose) {
1019                 content()
1020             }
1021             Text("Main content end")
1022             if (subcompose) {
1023                 DeferredSubcompose {
1024                     Column {
1025                         Text("Sub-composed content start")
1026                         content()
1027                         Text("Sub-composed content end")
1028                     }
1029                 }
1030             }
1031         }
1032 
1033         validate {
1034             Text("Main content start")
1035             if (!subcompose) {
1036                 Text("Movable content")
1037             }
1038             Text("Main content end")
1039             if (subcompose) {
1040                 DeferredSubcompose {
1041                     Column {
1042                         Text("Sub-composed content start")
1043                         Text("Movable content")
1044                         Text("Sub-composed content end")
1045                     }
1046                 }
1047             }
1048         }
1049 
1050         val expectedState = lastPrivateState
1051         subcompose = true
1052         expectChanges()
1053         revalidate()
1054 
1055         assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
1056 
1057         subcompose = false
1058         expectChanges()
1059         revalidate()
1060 
1061         assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
1062     }
1063 
1064     @Test // Regression test for 230830644
1065     fun deferredSubcompose_conditional_and_invalid() = compositionTest {
1066         var subcompose by mutableStateOf(false)
1067         var lastPrivateState: State<Int> = mutableStateOf(0)
1068         var state by mutableStateOf("one")
1069 
1070         val content = movableContentOf {
1071             lastPrivateState = remember { mutableStateOf(0) }
1072             Text("Movable content state: $state")
1073         }
1074 
1075         compose {
1076             Text("Main content start")
1077             if (!subcompose) {
1078                 content()
1079             }
1080             Text("Main content end")
1081             if (subcompose) {
1082                 DeferredSubcompose {
1083                     Text("Sub-composed content start")
1084                     content()
1085                     Text("Sub-composed content end")
1086                 }
1087             }
1088         }
1089 
1090         validate {
1091             Text("Main content start")
1092             if (!subcompose) {
1093                 Text("Movable content state: $state")
1094             }
1095             Text("Main content end")
1096             if (subcompose) {
1097                 DeferredSubcompose {
1098                     Text("Sub-composed content start")
1099                     Text("Movable content state: $state")
1100                     Text("Sub-composed content end")
1101                 }
1102             }
1103         }
1104 
1105         val expectedState = lastPrivateState
1106         subcompose = true
1107         state = "two"
1108         expectChanges()
1109         revalidate()
1110 
1111         assertEquals(expectedState, lastPrivateState)
1112     }
1113 
1114     @Test
1115     fun movableContentParameters_One() = compositionTest {
1116         val data = mutableStateOf(0)
1117         val content = movableContentOf<Int> { p1 -> Text("Value p1=$p1, data=${data.value}") }
1118 
1119         compose {
1120             content(1)
1121             content(2)
1122         }
1123 
1124         validate {
1125             Text("Value p1=1, data=${data.value}")
1126             Text("Value p1=2, data=${data.value}")
1127         }
1128 
1129         data.value++
1130         expectChanges()
1131         revalidate()
1132     }
1133 
1134     @Test
1135     fun movableContentParameters_Two() = compositionTest {
1136         val data = mutableStateOf(0)
1137         val content =
1138             movableContentOf<Int, Int> { p1, p2 ->
1139                 Text("Value p1=$p1, p2=$p2, data=${data.value}")
1140             }
1141 
1142         compose {
1143             content(1, 2)
1144             content(3, 4)
1145         }
1146 
1147         validate {
1148             Text("Value p1=1, p2=2, data=${data.value}")
1149             Text("Value p1=3, p2=4, data=${data.value}")
1150         }
1151 
1152         data.value++
1153         expectChanges()
1154         revalidate()
1155     }
1156 
1157     @Test
1158     fun movableContentParameters_Three() = compositionTest {
1159         val data = mutableStateOf(0)
1160         val content =
1161             movableContentOf<Int, Int, Int> { p1, p2, p3 ->
1162                 Text("Value p1=$p1, p2=$p2, p3=$p3, data=${data.value}")
1163             }
1164 
1165         compose {
1166             content(1, 2, 3)
1167             content(4, 5, 6)
1168         }
1169 
1170         validate {
1171             Text("Value p1=1, p2=2, p3=3, data=${data.value}")
1172             Text("Value p1=4, p2=5, p3=6, data=${data.value}")
1173         }
1174 
1175         data.value++
1176         expectChanges()
1177         revalidate()
1178     }
1179 
1180     @Test
1181     fun movableContentParameters_Four() = compositionTest {
1182         val data = mutableStateOf(0)
1183         val content =
1184             movableContentOf<Int, Int, Int, Int> { p1, p2, p3, p4 ->
1185                 Text("Value p1=$p1, p2=$p2, p3=$p3, p4=$p4, data=${data.value}")
1186             }
1187 
1188         compose {
1189             content(1, 2, 3, 4)
1190             content(5, 6, 7, 8)
1191         }
1192 
1193         validate {
1194             Text("Value p1=1, p2=2, p3=3, p4=4, data=${data.value}")
1195             Text("Value p1=5, p2=6, p3=7, p4=8, data=${data.value}")
1196         }
1197 
1198         data.value++
1199         expectChanges()
1200         revalidate()
1201     }
1202 
1203     @Test
1204     fun movableContentReceiver_None() = compositionTest {
1205         val data = mutableStateOf(0)
1206         val content =
1207             movableContentWithReceiverOf<Int>() { Text("Value this=$this, data=${data.value}") }
1208         val receiver1 = 100
1209         val receiver2 = 200
1210 
1211         compose {
1212             receiver1.content()
1213             receiver2.content()
1214         }
1215 
1216         validate {
1217             Text("Value this=100, data=${data.value}")
1218             Text("Value this=200, data=${data.value}")
1219         }
1220 
1221         data.value++
1222         expectChanges()
1223         revalidate()
1224     }
1225 
1226     @Test
1227     fun movableContentReceiver_One() = compositionTest {
1228         val data = mutableStateOf(0)
1229         val content =
1230             movableContentWithReceiverOf<Int, Int>() { p1 ->
1231                 Text("Value this=$this, p1=$p1, data=${data.value}")
1232             }
1233         val receiver1 = 100
1234         val receiver2 = 200
1235 
1236         compose {
1237             receiver1.content(1)
1238             receiver2.content(2)
1239         }
1240 
1241         validate {
1242             Text("Value this=100, p1=1, data=${data.value}")
1243             Text("Value this=200, p1=2, data=${data.value}")
1244         }
1245 
1246         data.value++
1247         expectChanges()
1248         revalidate()
1249     }
1250 
1251     @Test
1252     fun movableContentReceiver_Two() = compositionTest {
1253         val data = mutableStateOf(0)
1254         val content =
1255             movableContentWithReceiverOf<Int, Int, Int>() { p1, p2 ->
1256                 Text("Value this=$this, p1=$p1, p2=$p2, data=${data.value}")
1257             }
1258         val receiver1 = 100
1259         val receiver2 = 200
1260 
1261         compose {
1262             receiver1.content(1, 2)
1263             receiver2.content(3, 4)
1264         }
1265 
1266         validate {
1267             Text("Value this=100, p1=1, p2=2, data=${data.value}")
1268             Text("Value this=200, p1=3, p2=4, data=${data.value}")
1269         }
1270 
1271         data.value++
1272         expectChanges()
1273         revalidate()
1274     }
1275 
1276     @Test
1277     fun movableContentReceiver_Three() = compositionTest {
1278         val data = mutableStateOf(0)
1279         val content =
1280             movableContentWithReceiverOf<Int, Int, Int, Int>() { p1, p2, p3 ->
1281                 Text("Value this=$this, p1=$p1, p2=$p2, p3=$p3, data=${data.value}")
1282             }
1283         val receiver1 = 100
1284         val receiver2 = 200
1285 
1286         compose {
1287             receiver1.content(1, 2, 3)
1288             receiver2.content(4, 5, 6)
1289         }
1290 
1291         validate {
1292             Text("Value this=100, p1=1, p2=2, p3=3, data=${data.value}")
1293             Text("Value this=200, p1=4, p2=5, p3=6, data=${data.value}")
1294         }
1295 
1296         data.value++
1297         expectChanges()
1298         revalidate()
1299     }
1300 
1301     @Test
1302     fun movableContentParameters_changedParameter() = compositionTest {
1303         val data = mutableStateOf(0)
1304         val location = mutableStateOf(0)
1305         val content = movableContentOf<Int> { d -> Text("d=$d") }
1306 
1307         compose {
1308             if (location.value == 0) content(data.value)
1309             Column { if (location.value == 1) content(data.value) }
1310             Row { if (location.value == 2) content(data.value) }
1311         }
1312 
1313         validate {
1314             if (location.value == 0) Text("d=${data.value}")
1315             Column { if (location.value == 1) Text("d=${data.value}") }
1316             Row { if (location.value == 2) Text("d=${data.value}") }
1317         }
1318 
1319         location.value++
1320         data.value++
1321         expectChanges()
1322         revalidate()
1323 
1324         location.value++
1325         expectChanges()
1326         revalidate()
1327 
1328         location.value++
1329         data.value++
1330         expectChanges()
1331         revalidate()
1332     }
1333 
1334     @Test
1335     fun movableContentOfTheSameFunctionShouldHaveStableKeys() = compositionTest {
1336         val hashList1 = mutableListOf<CompositeKeyHashCode>()
1337         val hashList2 = mutableListOf<CompositeKeyHashCode>()
1338         val composable1: @Composable () -> Unit = { hashList1.add(currentCompositeKeyHashCode) }
1339         val composable2: @Composable () -> Unit = { hashList2.add(currentCompositeKeyHashCode) }
1340         val movableContent1A = movableContentOf(composable1)
1341         val movableContent1B = movableContentOf(composable1)
1342         val movableContent2A = movableContentOf(composable2)
1343         val movableContent2B = movableContentOf(composable2)
1344         compose {
1345             movableContent1A()
1346             movableContent1B()
1347             movableContent1A()
1348             movableContent1B()
1349             movableContent2A()
1350             movableContent2B()
1351             movableContent2A()
1352             movableContent2B()
1353         }
1354 
1355         fun List<CompositeKeyHashCode>.assertAllTheSame() = forEach { assertEquals(it, first()) }
1356         hashList1.assertAllTheSame()
1357         hashList2.assertAllTheSame()
1358         assertNotEquals(hashList1.first(), hashList2.first())
1359     }
1360 
1361     @Test
1362     fun keyInsideMovableContentShouldntChangeWhenRecomposed() = compositionTest {
1363         val hashList = mutableListOf<CompositeKeyHashCode>()
1364         val counter = mutableStateOf(0)
1365         val movableContent = movableContentOf {
1366             hashList.add(currentCompositeKeyHashCode)
1367             Text("counter=${counter.value}")
1368         }
1369         compose { movableContent() }
1370 
1371         validate { Text("counter=${counter.value}") }
1372 
1373         counter.value++
1374         expectChanges()
1375         revalidate()
1376 
1377         assertEquals(2, hashList.size)
1378         assertEquals(hashList[0], hashList[1])
1379     }
1380 
1381     @Test
1382     fun parameterPassingThroughDeferredSubcompose() = compositionTest {
1383         var state by mutableStateOf(false)
1384         var lastSeen: Boolean? = null
1385         val content = movableContentOf { parameter: Boolean -> Container { lastSeen = parameter } }
1386 
1387         compose {
1388             if (state) {
1389                 content(true)
1390             } else {
1391                 DeferredSubcompose { content(state) }
1392             }
1393         }
1394 
1395         advanceTimeBy(5_000)
1396 
1397         assertEquals(state, lastSeen)
1398 
1399         repeat(5) {
1400             state = !state
1401 
1402             expectChanges()
1403 
1404             assertEquals(state, lastSeen, "Failed in iteration $it")
1405         }
1406     }
1407 
1408     @Test
1409     fun stateChangesWhilePendingMove() = compositionTest {
1410         var state = 0
1411         var lastSeen: Int? = null
1412         var deferred by mutableStateOf(false)
1413         var scope: RecomposeScope? = null
1414 
1415         val content = movableContentOf {
1416             Container {
1417                 lastSeen = state
1418                 scope = currentRecomposeScope
1419             }
1420         }
1421 
1422         compose {
1423             if (deferred) {
1424                 DeferredSubcompose { content() }
1425                 SideEffect {
1426                     state++
1427                     scope?.invalidate()
1428                 }
1429             } else {
1430                 content()
1431             }
1432         }
1433 
1434         advanceTimeBy(5_000)
1435 
1436         assertEquals(state, lastSeen)
1437 
1438         deferred = true
1439 
1440         advance()
1441 
1442         assertEquals(state, lastSeen)
1443     }
1444 
1445     @Test
1446     fun movableContent_moveRow() = compositionTest {
1447         var condition by mutableStateOf(true)
1448 
1449         val movableContent1 = movableContentOf { Text("First") }
1450         val movableContent2 = movableContentOf { Text("Second") }
1451 
1452         compose {
1453             if (condition) {
1454                 Linear {
1455                     Linear { movableContent1() }
1456                     movableContent2()
1457                 }
1458             } else {
1459                 Linear {
1460                     Linear { movableContent1() }
1461                     movableContent2()
1462                 }
1463             }
1464         }
1465 
1466         validate {
1467             Linear {
1468                 Linear { Text("First") }
1469                 Text("Second")
1470             }
1471         }
1472 
1473         condition = false
1474         expectChanges()
1475 
1476         revalidate()
1477     }
1478 
1479     @Test
1480     fun movableContent_rememberOrdering() = compositionTest {
1481         val movableContent1 = movableContentOf { repeat(100) { Text("Some content") } }
1482         var includeContent by mutableStateOf(true)
1483         var rememberKey by mutableStateOf(0)
1484 
1485         compose {
1486             if (includeContent) {
1487                 movableContent1()
1488             }
1489             val a = remember(rememberKey) { SimpleRememberedObject("Key $rememberKey") }
1490             Text(a.name)
1491         }
1492 
1493         rememberKey++
1494         expectChanges()
1495 
1496         includeContent = false
1497         rememberKey++
1498         expectChanges()
1499     }
1500 
1501     @Test
1502     fun movableContent_nestedMovableContent() = compositionTest {
1503         var data = 0
1504 
1505         var condition by mutableStateOf(true)
1506 
1507         val nestedContent = movableContentOf {
1508             val state = remember { data++ }
1509             Text("Generated state: $state")
1510         }
1511 
1512         val contentHost = movableContentOf {
1513             Text("Host")
1514             if (condition) {
1515                 nestedContent()
1516             }
1517         }
1518 
1519         compose {
1520             if (condition) {
1521                 contentHost()
1522             }
1523             Text("Outer")
1524             if (!condition) {
1525                 contentHost()
1526                 nestedContent()
1527             }
1528         }
1529 
1530         validate {
1531             if (condition) {
1532                 Text("Host")
1533                 Text("Generated state: 0")
1534             }
1535             Text("Outer")
1536             if (!condition) {
1537                 Text("Host")
1538                 Text("Generated state: 0")
1539             }
1540         }
1541 
1542         condition = false
1543         expectChanges()
1544         revalidate()
1545         verifyConsistent()
1546 
1547         condition = true
1548         expectChanges()
1549         println("Done")
1550         revalidate()
1551         verifyConsistent()
1552     }
1553 
1554     @Test // 362539770
1555     fun movableContent_nestedMovableContent_direct() = compositionTest {
1556         var data = 0
1557 
1558         var condition by mutableStateOf(true)
1559 
1560         val common = movableContentOf {
1561             val state = remember { data++ }
1562             Text("Generated state: $state")
1563         }
1564 
1565         val wrapper = movableContentOf {
1566             Text("Wrapper start")
1567             common()
1568             Text("Wrapper end")
1569         }
1570 
1571         compose {
1572             Text("Outer")
1573             if (condition) {
1574                 wrapper()
1575             } else {
1576                 common()
1577             }
1578         }
1579 
1580         validate {
1581             Text("Outer")
1582             if (condition) {
1583                 Text("Wrapper start")
1584             }
1585             Text("Generated state: 0")
1586             if (condition) {
1587                 Text("Wrapper end")
1588             }
1589         }
1590 
1591         condition = false
1592         expectChanges()
1593         revalidate()
1594 
1595         condition = true
1596         expectChanges()
1597         revalidate()
1598     }
1599 
1600     @Test // 362539770
1601     @OptIn(ExperimentalComposeApi::class)
1602     fun movableContent_nestedMovableContent_disabled() = compositionTest {
1603         var data = 0
1604 
1605         var condition by mutableStateOf(true)
1606 
1607         val common = movableContentOf {
1608             val state = remember { data++ }
1609             Text("Generated state: $state")
1610         }
1611 
1612         val wrapper = movableContentOf {
1613             Text("Wrapper start")
1614             common()
1615             Text("Wrapper end")
1616         }
1617 
1618         compose {
1619             Text("Outer")
1620             if (condition) {
1621                 wrapper()
1622             } else {
1623                 common()
1624             }
1625         }
1626 
1627         var expectedState = 0
1628         validate {
1629             Text("Outer")
1630             if (condition) {
1631                 Text("Wrapper start")
1632             }
1633             Text("Generated state: $expectedState")
1634             if (condition) {
1635                 Text("Wrapper end")
1636             }
1637         }
1638 
1639         ComposeRuntimeFlags.isMovingNestedMovableContentEnabled = false
1640         try {
1641             // With moving nested content disabled the call to common() will generate new
1642             // state when it moves out of the containing movable content.
1643             expectedState = 1
1644             condition = false
1645             expectChanges()
1646             revalidate()
1647 
1648             condition = true
1649             expectChanges()
1650             revalidate()
1651         } finally {
1652             ComposeRuntimeFlags.isMovingNestedMovableContentEnabled = true
1653         }
1654     }
1655 
1656     @Test
1657     fun movableContent_nestedMovableContent_simpleMove() = compositionTest {
1658         var data = 0
1659 
1660         var condition by mutableStateOf(true)
1661 
1662         val common = movableContentOf {
1663             val state = remember { data++ }
1664             Text("Generated state: $state")
1665         }
1666 
1667         val wrapper = movableContentOf {
1668             Text("Wrapper start")
1669             common()
1670             Text("Wrapper end")
1671         }
1672 
1673         compose {
1674             Text("Outer")
1675             if (condition) {
1676                 Linear { wrapper() }
1677             } else {
1678                 wrapper()
1679             }
1680         }
1681 
1682         validate {
1683             Text("Outer")
1684             if (condition) {
1685                 Linear {
1686                     Text("Wrapper start")
1687                     Text("Generated state: 0")
1688                     Text("Wrapper end")
1689                 }
1690             } else {
1691                 Text("Wrapper start")
1692                 Text("Generated state: 0")
1693                 Text("Wrapper end")
1694             }
1695         }
1696 
1697         condition = false
1698         expectChanges()
1699         revalidate()
1700 
1701         condition = true
1702         expectChanges()
1703         revalidate()
1704     }
1705 
1706     @Test
1707     fun movableContent_nestedMovableContent_tree() = compositionTest {
1708         var data = 0
1709 
1710         @Composable
1711         fun Leaf() {
1712             val value = remember { data++ }
1713             Text("Data $value")
1714         }
1715 
1716         val level0 = Array(16) { movableContentOf { Leaf() } }
1717         val level1 =
1718             Array(8) { it ->
1719                 movableContentOf {
1720                     level0[it * 2]()
1721                     level0[it * 2 + 1]()
1722                 }
1723             }
1724         val level2 =
1725             Array(4) {
1726                 movableContentOf {
1727                     level1[it * 2]()
1728                     level1[it * 2 + 1]()
1729                 }
1730             }
1731         val level3 =
1732             Array(2) {
1733                 movableContentOf {
1734                     level2[it * 2]()
1735                     level2[it * 2 + 1]()
1736                 }
1737             }
1738 
1739         var displayTree by mutableStateOf(false)
1740 
1741         compose { if (displayTree) level3.forEach { it() } else level0.forEach { it() } }
1742 
1743         validate { repeat(16) { Text("Data $it") } }
1744 
1745         displayTree = true
1746         expectChanges()
1747         revalidate()
1748 
1749         displayTree = false
1750         expectChanges()
1751         revalidate()
1752     }
1753 
1754     @Test // 343178423
1755     fun movableContent_movingContentOutOfDeferredSubcomposition() = compositionTest {
1756         var toggle by mutableStateOf(true)
1757         val content = movableContentOf { Text("Toggle = $toggle") }
1758 
1759         compose {
1760             if (toggle) {
1761                 DeferredSubcompose { content() }
1762             } else {
1763                 content()
1764             }
1765         }
1766         advanceTimeBy(5_000)
1767 
1768         validate {
1769             if (toggle) {
1770                 DeferredSubcompose { Text("Toggle = $toggle") }
1771             } else {
1772                 Text("Toggle = false")
1773             }
1774         }
1775 
1776         toggle = !toggle
1777         expectChanges()
1778         revalidate()
1779     }
1780 
1781     @Test // 365802563
1782     fun movableContent_movingChildOfDeletedNode() = compositionTest {
1783         var index by mutableIntStateOf(0)
1784         val content = movableContentOf { Text("Some text") }
1785         compose {
1786             for (i in index..3) {
1787                 InlineLinear { content() }
1788             }
1789             for (i in 0..index) {
1790                 InlineLinear { content() }
1791             }
1792         }
1793         validate {
1794             for (i in index..3) {
1795                 Linear { Text("Some text") }
1796             }
1797             for (i in 0..index) {
1798                 Linear { Text("Some text") }
1799             }
1800         }
1801 
1802         index++
1803         advance()
1804 
1805         index++
1806         advance()
1807 
1808         index++
1809         advance()
1810     }
1811 }
1812 
1813 @Composable
Rownull1814 private fun Row(content: @Composable () -> Unit) {
1815     ComposeNode<View, ViewApplier>(factory = { View().also { it.name = "Row" } }, update = {}) {
1816         content()
1817     }
1818 }
1819 
Rownull1820 private fun MockViewValidator.Row(block: MockViewValidator.() -> Unit) {
1821     view("Row", block)
1822 }
1823 
1824 @Composable
Columnnull1825 private fun Column(content: @Composable () -> Unit) {
1826     ComposeNode<View, ViewApplier>(factory = { View().also { it.name = "Column" } }, update = {}) {
1827         content()
1828     }
1829 }
1830 
Emptynull1831 @Composable private fun Empty() {}
1832 
Columnnull1833 private fun MockViewValidator.Column(block: MockViewValidator.() -> Unit) {
1834     view("Column", block)
1835 }
1836 
1837 @Composable
Textnull1838 private fun Text(text: String) {
1839     ComposeNode<View, ViewApplier>(
1840         factory = { View().also { it.name = "Text" } },
1841         update = { set(text) { attributes["text"] = it } }
1842     )
1843 }
1844 
Textnull1845 private fun MockViewValidator.Text(text: String) {
1846     view("Text")
1847     assertEquals(text, view.attributes["text"])
1848 }
1849 
1850 @Composable
Markernull1851 private fun Marker() {
1852     ComposeNode<View, ViewApplier>(factory = { View().also { it.name = "Marker" } }, update = {})
1853 }
1854 
Markernull1855 private fun MockViewValidator.Marker() {
1856     view("Marker")
1857 }
1858 
1859 @Composable
Markernull1860 private fun Marker(value: Int) {
1861     ComposeNode<View, ViewApplier>(
1862         factory = { View().also { it.name = "Marker" } },
1863         update = { set(value) { attributes["value"] = it } }
1864     )
1865 }
1866 
1867 @Composable
Stacknull1868 private fun Stack(isHorizontal: Boolean, block: @Composable () -> Unit) {
1869     if (isHorizontal) {
1870         Column(block)
1871     } else {
1872         Row(block)
1873     }
1874 }
1875 
MockViewValidatornull1876 private fun MockViewValidator.Stack(isHorizontal: Boolean, block: MockViewValidator.() -> Unit) {
1877     if (isHorizontal) {
1878         Column(block)
1879     } else {
1880         Row(block)
1881     }
1882 }
1883 
1884 @Composable
DisplayIntnull1885 private fun DisplayInt(value: State<Int>) {
1886     Text("value=${value.value}")
1887 }
1888 
DisplayIntnull1889 private fun MockViewValidator.DisplayInt(value: State<Int>) {
1890     Text("value=${value.value}")
1891 }
1892 
Markernull1893 private fun MockViewValidator.Marker(value: Int) {
1894     view("Marker")
1895     assertEquals(value, view.attributes["value"])
1896 }
1897 
1898 @Composable
Subcomposenull1899 private fun Subcompose(content: @Composable () -> Unit) {
1900     val host = View().also { it.name = "SubcomposeHost" }
1901     ComposeNode<View, ViewApplier>(factory = { host }, update = {})
1902     val parent = rememberCompositionContext()
1903     val composition = Composition(ViewApplier(host), parent)
1904     composition.setContent(content)
1905     DisposableEffect(Unit) { onDispose { composition.dispose() } }
1906 }
1907 
1908 @Composable
DeferredSubcomposenull1909 private fun DeferredSubcompose(content: @Composable () -> Unit) {
1910     val host = View().also { it.name = "DeferredSubcompose" }
1911     ComposeNode<View, ViewApplier>(factory = { host }, update = {})
1912     val parent = rememberCompositionContext()
1913     val composition = remember { Composition(ViewApplier(host), parent) }
1914     LaunchedEffect(content as Any) { composition.setContent(content) }
1915     DisposableEffect(Unit) { onDispose { composition.dispose() } }
1916 }
1917 
Subcomposenull1918 private fun MockViewValidator.Subcompose(content: MockViewValidator.() -> Unit) {
1919     view("SubcomposeHost", content)
1920 }
1921 
MockViewValidatornull1922 private fun MockViewValidator.DeferredSubcompose(content: MockViewValidator.() -> Unit) {
1923     view("DeferredSubcompose", content)
1924 }
1925 
1926 class RememberedObject : RememberObserver {
1927     var count: Int = 0
1928     val isLive: Boolean
1929         get() = count > 0
1930 
1931     private var rememberedCount = 0
1932     private var forgottenCount = 0
1933     private var abandonedCount = 0
1934 
1935     private var died: Boolean = false
1936 
onRememberednull1937     override fun onRemembered() {
1938         check(!died) { "Remember observer was resurrected" }
1939         rememberedCount++
1940         count++
1941     }
1942 
onForgottennull1943     override fun onForgotten() {
1944         check(count > 0) { "Abandoned or forgotten more times than remembered" }
1945         forgottenCount++
1946         count--
1947         if (count == 0) died = true
1948     }
1949 
onAbandonednull1950     override fun onAbandoned() {
1951         check(count > 0) { "Abandoned or forgotten more times than remembered" }
1952         abandonedCount++
1953         count--
1954         if (count == 0) died = true
1955     }
1956 }
1957 
1958 class SimpleRememberedObject(val name: String) : RememberObserver {
onRememberednull1959     override fun onRemembered() {}
1960 
onForgottennull1961     override fun onForgotten() {}
1962 
onAbandonednull1963     override fun onAbandoned() {}
1964 }
1965