@@ -15,6 +15,7 @@ import androidx.compose.runtime.setValue
15
15
import androidx.compose.ui.Modifier
16
16
import androidx.compose.ui.platform.ComposeView
17
17
import androidx.compose.ui.platform.ViewCompositionStrategy
18
+ import androidx.compose.ui.platform.ViewCompositionStrategy.Companion
18
19
import androidx.compose.ui.platform.ViewCompositionStrategy.DisposeOnDetachedFromWindow
19
20
import androidx.compose.ui.platform.ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
20
21
import androidx.compose.ui.platform.testTag
@@ -65,15 +66,16 @@ internal class ComposeViewTreeIntegrationTest {
65
66
66
67
@Before fun setUp () {
67
68
scenario.onActivity {
68
- it.viewEnvironment = ViewEnvironment .EMPTY + ViewRegistry (NoTransitionBackStackContainer )
69
+ it.viewEnvironment = (ViewEnvironment .EMPTY + ViewRegistry (NoTransitionBackStackContainer ))
70
+ .withComposeInteropSupport()
69
71
}
70
72
}
71
73
72
74
@Test fun compose_view_assertions_work () {
73
- val firstScreen = TestComposeRendering (" first" ) {
75
+ val firstScreen = VanillaComposeRendering (" first" ) {
74
76
BasicText (" First Screen" )
75
77
}
76
- val secondScreen = TestComposeRendering (" second" ) {}
78
+ val secondScreen = VanillaComposeRendering (" second" ) {}
77
79
78
80
scenario.onActivity {
79
81
it.setBackstack(firstScreen)
@@ -89,18 +91,87 @@ internal class ComposeViewTreeIntegrationTest {
89
91
composeRule.onNodeWithText(" First Screen" ).assertDoesNotExist()
90
92
}
91
93
92
- @Test fun composition_is_disposed_when_navigated_away_dispose_on_detach_strategy () {
94
+ @Test fun composition_is_disposed_when_navigated_away_stock_class () {
93
95
var composedCount = 0
94
96
var disposedCount = 0
95
- val firstScreen = TestComposeRendering (" first" , disposeStrategy = DisposeOnDetachedFromWindow ) {
96
- DisposableEffect (Unit ) {
97
- composedCount++
98
- onDispose {
99
- disposedCount++
97
+ val firstScreen =
98
+ VanillaComposeRendering (" first" ) {
99
+ DisposableEffect (Unit ) {
100
+ composedCount++
101
+ onDispose {
102
+ disposedCount++
103
+ }
104
+ }
105
+ }
106
+ val secondScreen = VanillaComposeRendering (" second" ) {}
107
+
108
+ scenario.onActivity {
109
+ it.setBackstack(firstScreen)
110
+ }
111
+
112
+ composeRule.runOnIdle {
113
+ assertThat(composedCount).isEqualTo(1 )
114
+ assertThat(disposedCount).isEqualTo(0 )
115
+ }
116
+
117
+ // Navigate away.
118
+ scenario.onActivity {
119
+ it.setBackstack(firstScreen, secondScreen)
120
+ }
121
+
122
+ composeRule.runOnIdle {
123
+ assertThat(composedCount).isEqualTo(1 )
124
+ assertThat(disposedCount).isEqualTo(1 )
125
+ }
126
+ }
127
+
128
+ @Test fun composition_is_disposed_when_navigated_away_default_strategy () {
129
+ var composedCount = 0
130
+ var disposedCount = 0
131
+ val firstScreen =
132
+ BespokeComposeRendering (" first" , disposeStrategy = ViewCompositionStrategy .Default ) {
133
+ DisposableEffect (Unit ) {
134
+ composedCount++
135
+ onDispose {
136
+ disposedCount++
137
+ }
100
138
}
101
139
}
140
+ val secondScreen = VanillaComposeRendering (" second" ) {}
141
+
142
+ scenario.onActivity {
143
+ it.setBackstack(firstScreen)
144
+ }
145
+
146
+ composeRule.runOnIdle {
147
+ assertThat(composedCount).isEqualTo(1 )
148
+ assertThat(disposedCount).isEqualTo(0 )
102
149
}
103
- val secondScreen = TestComposeRendering (" second" ) {}
150
+
151
+ // Navigate away.
152
+ scenario.onActivity {
153
+ it.setBackstack(firstScreen, secondScreen)
154
+ }
155
+
156
+ composeRule.runOnIdle {
157
+ assertThat(composedCount).isEqualTo(1 )
158
+ assertThat(disposedCount).isEqualTo(1 )
159
+ }
160
+ }
161
+
162
+ @Test fun composition_is_disposed_when_navigated_away_dispose_on_detach_strategy () {
163
+ var composedCount = 0
164
+ var disposedCount = 0
165
+ val firstScreen =
166
+ BespokeComposeRendering (" first" , disposeStrategy = DisposeOnDetachedFromWindow ) {
167
+ DisposableEffect (Unit ) {
168
+ composedCount++
169
+ onDispose {
170
+ disposedCount++
171
+ }
172
+ }
173
+ }
174
+ val secondScreen = VanillaComposeRendering (" second" ) {}
104
175
105
176
scenario.onActivity {
106
177
it.setBackstack(firstScreen)
@@ -126,15 +197,15 @@ internal class ComposeViewTreeIntegrationTest {
126
197
var composedCount = 0
127
198
var disposedCount = 0
128
199
val firstScreen =
129
- TestComposeRendering (" first" , disposeStrategy = DisposeOnViewTreeLifecycleDestroyed ) {
200
+ BespokeComposeRendering (" first" , disposeStrategy = DisposeOnViewTreeLifecycleDestroyed ) {
130
201
DisposableEffect (Unit ) {
131
202
composedCount++
132
203
onDispose {
133
204
disposedCount++
134
205
}
135
206
}
136
207
}
137
- val secondScreen = TestComposeRendering (" second" ) {}
208
+ val secondScreen = VanillaComposeRendering (" second" ) {}
138
209
139
210
scenario.onActivity {
140
211
it.setBackstack(firstScreen)
@@ -157,7 +228,7 @@ internal class ComposeViewTreeIntegrationTest {
157
228
}
158
229
159
230
@Test fun composition_state_is_restored_after_config_change () {
160
- val firstScreen = TestComposeRendering (" first" ) {
231
+ val firstScreen = VanillaComposeRendering (" first" ) {
161
232
var counter by rememberSaveable { mutableStateOf(0 ) }
162
233
BasicText (
163
234
" Counter: $counter " ,
@@ -184,7 +255,7 @@ internal class ComposeViewTreeIntegrationTest {
184
255
}
185
256
186
257
@Test fun composition_state_is_restored_after_navigating_back () {
187
- val firstScreen = TestComposeRendering (" first" ) {
258
+ val firstScreen = VanillaComposeRendering (" first" ) {
188
259
var counter by rememberSaveable { mutableStateOf(0 ) }
189
260
BasicText (
190
261
" Counter: $counter " ,
@@ -193,7 +264,7 @@ internal class ComposeViewTreeIntegrationTest {
193
264
.testTag(CounterTag )
194
265
)
195
266
}
196
- val secondScreen = TestComposeRendering (" second" ) {
267
+ val secondScreen = VanillaComposeRendering (" second" ) {
197
268
BasicText (" nothing to see here" )
198
269
}
199
270
@@ -225,7 +296,7 @@ internal class ComposeViewTreeIntegrationTest {
225
296
226
297
@Test
227
298
fun composition_state_is_restored_after_config_change_then_navigating_back () {
228
- val firstScreen = TestComposeRendering (" first" ) {
299
+ val firstScreen = VanillaComposeRendering (" first" ) {
229
300
var counter by rememberSaveable { mutableStateOf(0 ) }
230
301
BasicText (
231
302
" Counter: $counter " ,
@@ -234,7 +305,7 @@ internal class ComposeViewTreeIntegrationTest {
234
305
.testTag(CounterTag )
235
306
)
236
307
}
237
- val secondScreen = TestComposeRendering (" second" ) {
308
+ val secondScreen = VanillaComposeRendering (" second" ) {
238
309
BasicText (" nothing to see here" )
239
310
}
240
311
@@ -267,7 +338,7 @@ internal class ComposeViewTreeIntegrationTest {
267
338
}
268
339
269
340
@Test fun composition_state_is_not_restored_after_screen_is_removed_from_backstack () {
270
- val firstScreen = TestComposeRendering (" first" ) {
341
+ val firstScreen = VanillaComposeRendering (" first" ) {
271
342
var counter by rememberSaveable { mutableStateOf(0 ) }
272
343
BasicText (
273
344
" Counter: $counter " ,
@@ -276,7 +347,7 @@ internal class ComposeViewTreeIntegrationTest {
276
347
.testTag(CounterTag )
277
348
)
278
349
}
279
- val secondScreen = TestComposeRendering (" second" ) {
350
+ val secondScreen = VanillaComposeRendering (" second" ) {
280
351
BasicText (" nothing to see here" )
281
352
}
282
353
@@ -312,7 +383,7 @@ internal class ComposeViewTreeIntegrationTest {
312
383
313
384
@Test
314
385
fun composition_state_is_not_restored_after_screen_is_removed_and_replaced_from_backstack () {
315
- val firstScreen = TestComposeRendering (" first" ) {
386
+ val firstScreen = VanillaComposeRendering (" first" ) {
316
387
var counter by rememberSaveable { mutableStateOf(0 ) }
317
388
BasicText (
318
389
" Counter: $counter " ,
@@ -321,7 +392,7 @@ internal class ComposeViewTreeIntegrationTest {
321
392
.testTag(CounterTag )
322
393
)
323
394
}
324
- val secondScreen = TestComposeRendering (" second" ) {
395
+ val secondScreen = VanillaComposeRendering (" second" ) {
325
396
BasicText (" nothing to see here" )
326
397
}
327
398
@@ -360,8 +431,8 @@ internal class ComposeViewTreeIntegrationTest {
360
431
.assertTextEquals(" Counter: 0" )
361
432
}
362
433
363
- @Test fun composition_is_restored_in_modal_after_config_change () {
364
- val firstScreen: Screen = TestComposeRendering (compatibilityKey = " " ) {
434
+ @Test fun composition_is_restored_in_overlay_after_config_change () {
435
+ val firstScreen: Screen = VanillaComposeRendering (compatibilityKey = " " ) {
365
436
var counter by rememberSaveable { mutableStateOf(0 ) }
366
437
BasicText (
367
438
" Counter: $counter " ,
@@ -392,8 +463,8 @@ internal class ComposeViewTreeIntegrationTest {
392
463
.assertTextEquals(" Counter: 1" )
393
464
}
394
465
395
- @Test fun composition_is_restored_in_multiple_modals_after_config_change () {
396
- val firstScreen: Screen = TestComposeRendering (compatibilityKey = " 0" ) {
466
+ @Test fun composition_is_restored_in_multiple_overlays_after_config_change () {
467
+ val firstScreen: Screen = VanillaComposeRendering (compatibilityKey = " 0" ) {
397
468
var counter by rememberSaveable { mutableStateOf(0 ) }
398
469
BasicText (
399
470
" Counter: $counter " ,
@@ -403,7 +474,7 @@ internal class ComposeViewTreeIntegrationTest {
403
474
)
404
475
}
405
476
406
- val secondScreen: Screen = TestComposeRendering (compatibilityKey = " 1" ) {
477
+ val secondScreen: Screen = VanillaComposeRendering (compatibilityKey = " 1" ) {
407
478
var counter by rememberSaveable { mutableStateOf(0 ) }
408
479
BasicText (
409
480
" Counter2: $counter " ,
@@ -413,7 +484,7 @@ internal class ComposeViewTreeIntegrationTest {
413
484
)
414
485
}
415
486
416
- val thirdScreen: Screen = TestComposeRendering (compatibilityKey = " 2" ) {
487
+ val thirdScreen: Screen = VanillaComposeRendering (compatibilityKey = " 2" ) {
417
488
var counter by rememberSaveable { mutableStateOf(0 ) }
418
489
BasicText (
419
490
" Counter3: $counter " ,
@@ -464,12 +535,12 @@ internal class ComposeViewTreeIntegrationTest {
464
535
.assertTextEquals(" Counter3: 1" )
465
536
}
466
537
467
- @Test fun composition_is_restored_in_multiple_modals_backstacks_after_config_change () {
538
+ @Test fun composition_is_restored_in_multiple_overlays_backstacks_after_config_change () {
468
539
fun createRendering (
469
540
layer : Int ,
470
541
screen : Int
471
- ) = TestComposeRendering (
472
- // Use the same compatibility key across layers – these screens are in different modals , so
542
+ ) = VanillaComposeRendering (
543
+ // Use the same compatibility key across layers – these screens are in different overlays , so
473
544
// they won't conflict.
474
545
compatibilityKey = screen.toString()
475
546
) {
@@ -494,7 +565,7 @@ internal class ComposeViewTreeIntegrationTest {
494
565
EmptyRendering ,
495
566
listOf (
496
567
TestOverlay (BackStackScreen (EmptyRendering , layer0Screen0)),
497
- // A SavedStateRegistry is set up for each modal . Each registry needs a unique name,
568
+ // A SavedStateRegistry is set up for each overlay . Each registry needs a unique name,
498
569
// and these names default to their `Compatible.keyFor` value. When we show two
499
570
// of the same type at the same time, we need to give them unique names.
500
571
TestOverlay (NamedScreen (BackStackScreen (EmptyRendering , layer1Screen0), " another" ))
@@ -522,7 +593,7 @@ internal class ComposeViewTreeIntegrationTest {
522
593
EmptyRendering ,
523
594
listOf (
524
595
TestOverlay (BackStackScreen (EmptyRendering , layer0Screen0, layer0Screen1)),
525
- // A SavedStateRegistry is set up for each modal . Each registry needs a unique name,
596
+ // A SavedStateRegistry is set up for each overlay . Each registry needs a unique name,
526
597
// and these names default to their `Compatible.keyFor` value. When we show two
527
598
// of the same type at the same time, we need to give them unique names.
528
599
TestOverlay (
@@ -566,7 +637,7 @@ internal class ComposeViewTreeIntegrationTest {
566
637
EmptyRendering ,
567
638
listOf (
568
639
TestOverlay (BackStackScreen (EmptyRendering , layer0Screen0)),
569
- // A SavedStateRegistry is set up for each modal . Each registry needs a unique name,
640
+ // A SavedStateRegistry is set up for each overlay . Each registry needs a unique name,
570
641
// and these names default to their `Compatible.keyFor` value. When we show two
571
642
// of the same type at the same time, we need to give them unique names.
572
643
TestOverlay (NamedScreen (BackStackScreen (EmptyRendering , layer1Screen0), " another" ))
@@ -581,7 +652,7 @@ internal class ComposeViewTreeIntegrationTest {
581
652
.assertIsDisplayed()
582
653
}
583
654
584
- private fun WorkflowUiTestActivity.setBackstack (vararg backstack : TestComposeRendering ) {
655
+ private fun WorkflowUiTestActivity.setBackstack (vararg backstack : Screen ) {
585
656
setRendering(
586
657
BackStackScreen .fromList(listOf<AndroidScreen <* >>(EmptyRendering ) + backstack.asList())
587
658
)
@@ -598,20 +669,27 @@ internal class ComposeViewTreeIntegrationTest {
598
669
}
599
670
}
600
671
601
- data class TestComposeRendering (
672
+ /* *
673
+ * This is our own custom lovingly handcrafted implementation that creates [ComposeView]
674
+ * itself, bypassing [ScreenComposableFactory] entirely. Allows us to mess with alternative
675
+ * [ViewCompositionStrategy] approaches.
676
+ */
677
+ data class BespokeComposeRendering (
602
678
override val compatibilityKey : String ,
603
679
val disposeStrategy : ViewCompositionStrategy ? = null ,
604
680
val content : @Composable () -> Unit
605
- ) : Compatible, AndroidScreen<TestComposeRendering>, ScreenViewFactory<TestComposeRendering> {
606
- override val type: KClass <in TestComposeRendering > = TestComposeRendering ::class
607
- override val viewFactory: ScreenViewFactory <TestComposeRendering > get() = this
681
+ ) : Compatible,
682
+ AndroidScreen <BespokeComposeRendering >,
683
+ ScreenViewFactory <BespokeComposeRendering > {
684
+ override val type: KClass <in BespokeComposeRendering > = BespokeComposeRendering ::class
685
+ override val viewFactory: ScreenViewFactory <BespokeComposeRendering > get() = this
608
686
609
687
override fun buildView (
610
- initialRendering : TestComposeRendering ,
688
+ initialRendering : BespokeComposeRendering ,
611
689
initialEnvironment : ViewEnvironment ,
612
690
context : Context ,
613
691
container : ViewGroup ?
614
- ): ScreenViewHolder <TestComposeRendering > {
692
+ ): ScreenViewHolder <BespokeComposeRendering > {
615
693
var lastCompositionStrategy = initialRendering.disposeStrategy
616
694
617
695
return ComposeView (context).let { view ->
@@ -629,6 +707,19 @@ internal class ComposeViewTreeIntegrationTest {
629
707
}
630
708
}
631
709
710
+ /* *
711
+ * Bog standard [ComposeScreen], as opposed to [BespokeComposeRendering].
712
+ * Requires [ViewEnvironment.withComposeInteropSupport].
713
+ */
714
+ data class VanillaComposeRendering (
715
+ override val compatibilityKey : String ,
716
+ val content : @Composable () -> Unit
717
+ ) : Compatible, ComposeScreen {
718
+ @Composable override fun Content () {
719
+ content()
720
+ }
721
+ }
722
+
632
723
object EmptyRendering : AndroidScreen<EmptyRendering> {
633
724
override val viewFactory: ScreenViewFactory <EmptyRendering >
634
725
get() = ScreenViewFactory .fromCode { _, e, c, _ ->
0 commit comments