Slide 1

Slide 1 text

Compose Shibuya.apk # 5 1 田 一 (@mr_mkeeda)

Slide 2

Slide 2 text

田 一 ( ) 𝕏 : @mr_mkeeda Github: @mkeeda Android Engineer at Cybozu, Inc 💣 2

Slide 3

Slide 3 text

Recomposition 3

Slide 4

Slide 4 text

Compose 4 @Test fun successfulTest() = runComposeUiTest { var flag by mutableStateOf(false) setContent { if (flag) { Text("Hello") } else { Text("World") } } onNodeWithText("Hello").assertDoesNotExist() onNodeWithText("World").assertExists() flag = true onNodeWithText("Hello").assertExists() onNodeWithText("World").assertDoesNotExist() }

Slide 5

Slide 5 text

手 5 Side e ff ects (LaunchedE ff ect, produceState) mainClock.autoAdvance = false mainClock.advanceTimeBy(3000) mainClock.advanceTimeByFrame()

Slide 6

Slide 6 text

6 var isRunning by mutableStateOf(false) var taskStarted = false var taskFinished = false setContent { if (isRunning) { LaunchedEffect(Unit) { taskStarted = true delay(3000) taskFinished = true } } } mainClock.autoAdvance = false isRunning = true mainClock.advanceTimeBy(1500) // ൒෼͚ͩ࣌ؒΛਐΊΔ
 taskStarted shouldBe true // λεΫ͸։͍࢝ͯ͠Δ͚Ͳ taskFinished shouldBe false // ऴΘͬͯͳ͍ mainClock.advanceTimeBy(1500) // ׬ྃ࣌ࠁ·ͰਐΊΔ taskFinished shouldBe true // λεΫ͸ऴΘ͍ͬͯΔ FAILED ❌ testStarted = false

Slide 7

Slide 7 text

7 mainClock.autoAdvance = false isRunning = true waitForIdle() // ⭐ mainClock.advanceTimeByFrame() // ⭐ mainClock.advanceTimeBy(1500) // ൒෼͚ͩ࣌ؒΛਐΊΔ
 taskStarted shouldBe true // λεΫ͸։͍࢝ͯ͠Δ͚Ͳ taskFinished shouldBe false // ऴΘͬͯͳ͍ mainClock.advanceTimeBy(1500) // ׬ྃ࣌ࠁ·ͰਐΊΔ taskFinished shouldBe true // λεΫ͸ऴΘ͍ͬͯΔ SUCCESS ✅ var isRunning by mutableStateOf(false) var taskStarted = false var taskFinished = false setContent { if (isRunning) { LaunchedEffect(Unit) { taskStarted = true delay(3000) taskFinished = true } } }

Slide 8

Slide 8 text

8 mainClock.autoAdvance = false isRunning = true mainClock.advanceTimeByFrame() // ⭐ waitForIdle() // ⭐ mainClock.advanceTimeBy(1500) // ൒෼͚ͩ࣌ؒΛਐΊΔ
 taskStarted shouldBe true // λεΫ͸։͍࢝ͯ͠Δ͚Ͳ taskFinished shouldBe false // ऴΘͬͯͳ͍ mainClock.advanceTimeBy(1500) // ׬ྃ࣌ࠁ·ͰਐΊΔ taskFinished shouldBe true // λεΫ͸ऴΘ͍ͬͯΔ var isRunning by mutableStateOf(false) var taskStarted = false var taskFinished = false setContent { if (isRunning) { LaunchedEffect(Unit) { taskStarted = true delay(3000) taskFinished = true } } } FAILED ❌ testStarted = false

Slide 9

Slide 9 text

9 mainClock.autoAdvance = false isRunning = true mainClock.advanceTimeByFrame() // ⭐ mainClock.advanceTimeBy(1500) // ൒෼͚ͩ࣌ؒΛਐΊΔ
 taskStarted shouldBe true // λεΫ͸։͍࢝ͯ͠Δ͚Ͳ taskFinished shouldBe false // ऴΘͬͯͳ͍ mainClock.advanceTimeBy(1500) // ׬ྃ࣌ࠁ·ͰਐΊΔ taskFinished shouldBe true // λεΫ͸ऴΘ͍ͬͯΔ var isRunning by mutableStateOf(false) var taskStarted = false var taskFinished = false setContent { if (isRunning) { LaunchedEffect(Unit) { taskStarted = true delay(3000) taskFinished = true } } } FAILED ❌ testStarted = false

Slide 10

Slide 10 text

10 mainClock.autoAdvance = false isRunning = true waitForIdle() // ⭐ mainClock.advanceTimeBy(1500) // ൒෼͚ͩ࣌ؒΛਐΊΔ
 taskStarted shouldBe true // λεΫ͸։͍࢝ͯ͠Δ͚Ͳ taskFinished shouldBe false // ऴΘͬͯͳ͍ mainClock.advanceTimeBy(1500) // ׬ྃ࣌ࠁ·ͰਐΊΔ taskFinished shouldBe true // λεΫ͸ऴΘ͍ͬͯΔ var isRunning by mutableStateOf(false) var taskStarted = false var taskFinished = false setContent { if (isRunning) { LaunchedEffect(Unit) { taskStarted = true delay(3000) taskFinished = true } } } FAILED ❌ testStarted = false

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

Frame 面 1 60 fps 1 60 frame 生 12 Frame

Slide 13

Slide 13 text

13 MainTestClock Test ComposeTestRule Monotonic FrameClock Recomposer MainTestClock.autoAdvance = false Frame

Slide 14

Slide 14 text

14 MainTestClock Test ComposeTestRule Monotonic FrameClock state Composition Recomposer MainTestClock.autoAdvance = false Frame

Slide 15

Slide 15 text

15 MainTestClock Test ComposeTestRule Monotonic FrameClock state Composition waitForIdle() Recomposer Frame Recompose MainTestClock.autoAdvance = false

Slide 16

Slide 16 text

16 MainTestClock Test ComposeTestRule Monotonic FrameClock state Composition waitForIdle() Recomposer Frame Recompose advanceTimeByFrame() Frame recompose MainTestClock.autoAdvance = false

Slide 17

Slide 17 text

MonotonicFrameClock Frame withFrameNanos() Frame suspend onFrame 1Frame withFrameNanos() Frame 17 interface MonotonicFrameClock : CoroutineContext.Element { suspend fun withFrameNanos( onFrame: (frameTimeNanos: Long) -> R ): R }

Slide 18

Slide 18 text

MonotonicFrameClock AndroidUiFrameClock Android 用 TestMonotonicFrameClock Frame Coroutine delay kotlinx.coroutines.test.TestCoroutineScheduler Frame 18 Frame 1 Frame 2 Frame 2 delay( 16 mills) delay( 16 mills) advanceTimeBy( 32 mills)

Slide 19

Slide 19 text

Recomposition Frame Frame MonotonicFrameClock Recomposition Frame 19

Slide 20

Slide 20 text

20

Slide 21

Slide 21 text

21 MainTestClock Test ComposeTestRule Monotonic FrameClock state Composition waitForIdle() Recomposer Frame Recompose advanceTimeByFrame() Frame recompose MainTestClock.autoAdvance = true onNode onNodeWithText("Hello").assertDoesNotExist()