Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Androidアプリで安定して動作させ継続的に開発するために設計の原則を利用して開発した話

takahirom
March 16, 2023

 Androidアプリで安定して動作させ継続的に開発するために設計の原則を利用して開発した話

takahirom

March 16, 2023
Tweet

More Decks by takahirom

Other Decks in Programming

Transcript

  1. AbemaTV, Inc. All Rights Reserved AbemaTV, Inc. All Rights Reserved

    1 AndroidΞϓϦͰɺ 
 ҆ఆͯ͠ಈ࡞ͤ͞ ܧଓతʹ։ൃ͢ΔͨΊʹɺ 
 ઃܭͷݪଇΛར༻ͯ͠ɺ 
 ։ൃͨ͠࿩ 2023 March 16th גࣜձࣾαΠόʔΤʔδΣϯτ takahirom(@new_runnable)
  2. AbemaTV, Inc. All Rights Reserved ࣗݾ঺հ 2 ໟडਸ༸ (ΊΜ͡Ύ͔ͨͻΖ ͱಡΈ·͢)

    takahiromͱ͍͏໊લͰΑ͘Qiitaॻ͍ͨΓOSS ࡞ͬͨΓ͍ͯ͠·͢ɻ ABEMAɺAndroidΤϯδχΞ 
 CyberAgent Developer Expert Google Developers Expert for Android DroidKaigi Co-Organizer, App Leader
  3. AbemaTV, Inc. All Rights Reserved ࠓճ঺հ͢Δ࣮૷Λಋೖͨ͠ը໘ 3 ػೳ • ์ૹͷࢹௌ

    • ௥͔͚ͬ࠶ੜͷࢹௌ • ݟಀ͠഑৴ࢹௌ • ϚϧνΞϯάϧ • ίϝϯτ • ͳͲ FIFA ϫʔϧυΧοϓ Χλʔϧ 2022 ޙ΋ɺ 
 ܧଓͯ͠ར༻͍ͯ͘͠ը໘ FIFA ϫʔϧυΧοϓ Χλʔϧ 2022 ʹ޲͚ͯ৽ن Ͱ࡞੒ͨ͠ը໘ ABEMAͰFIFA ϫʔϧυΧοϓΛࢹௌ͢Δํ๏͸ʁϚϧνΞϯάϧɺݟಀ͠഑৴ɺ௥͔͚ͬ࠶ੜͳͲศརͳػೳΛ͝঺հ https://times.abema.tv/fifaworldcup/articles/-/10036755 “ABEMAͰϫʔϧυΧοϓ2022Λࢹௌ͢Δํ๏ʂྉۚ͸͍͘Βʁ࿥ը͸Ͱ͖Δʁ”ΑΓ https://times.abema.tv/fifaworldcup/articles/-/10048409
  4. AbemaTV, Inc. All Rights Reserved ࠓճͷ“ઃܭͷݪଇ”ͱ͸ 4 AndroidͷެࣜαΠτʹ 
 AndroidͷΞϓϦͷઃܭΨΠυ͕͋Γɺ

    ͦͷதʹ͍͔ͭ͘ݪଇ͕ࡌ͍ͬͯͯɺ ͦΕΛࢀߟʹ͠ͳ͕Β ABEMAͰͲ͏࡞ͬͨͷ͔Λઆ໌͍͖ͯ͠· ͢ɻ ͢΂ͯͷΞϓϦͰ࢖͑Δ΋ͷͰ͸ͳ͘ɺ 
 վળ఺͕·ͩ·ͩ͋Δͱࢥ͏ͷͰɺ 
 ͜͏͍͏΍Γํ΋͋Δͷ͔ͱ͍͏͙Β͍Ͱײ͡ Ͱݟ͍͚ͯͨͩΔͱΑ͍͔΋͠Ε·ͤΜɻ Android DevelopersͷGuide to app architectureͳͲʹ 
 ܝࡌ͞Ε͍ͯΔ΋ͷ Guide to app architecture https://developer.android.com/topic/architecture
  5. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 https://developer.android.com/topic/architecture#single-source-of-truth
  6. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ https://developer.android.com/topic/architecture#single-source-of-truth
  7. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ https://developer.android.com/topic/architecture#single-source-of-truth
  8. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ https://developer.android.com/topic/architecture#single-source-of-truth
  9. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ ◦ SSoT͚͕ͩσʔλΛมߋͰ͖Δ https://developer.android.com/topic/architecture#single-source-of-truth
  10. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ ◦ SSoT͚͕ͩσʔλΛมߋͰ͖Δ ◦ SSoT͸ImmutableͳσʔλΛެ։͢Δ https://developer.android.com/topic/architecture#single-source-of-truth
  11. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ ◦ SSoT͚͕ͩσʔλΛมߋͰ͖Δ ◦ SSoT͸ImmutableͳσʔλΛެ։͢Δ ◦ SSoT͸֎͔ΒσʔλΛมߋՄೳͳؔ਺͔ ΠϕϯτΛड͚औΔ https://developer.android.com/topic/architecture#single-source-of-truth
  12. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ ◦ SSoT͚͕ͩσʔλΛมߋͰ͖Δ ◦ SSoT͸ImmutableͳσʔλΛެ։͢Δ ◦ SSoT͸֎͔ΒσʔλΛมߋՄೳͳؔ਺͔ ΠϕϯτΛड͚औΔ • σʔλͷूத؅ཧ https://developer.android.com/topic/architecture#single-source-of-truth
  13. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ ◦ SSoT͚͕ͩσʔλΛมߋͰ͖Δ ◦ SSoT͸ImmutableͳσʔλΛެ։͢Δ ◦ SSoT͸֎͔ΒσʔλΛมߋՄೳͳؔ਺͔ ΠϕϯτΛड͚औΔ • σʔλͷूத؅ཧ • ଞͷͱ͜Ζ͔Βσʔλ͕มߋ͞Εͳ ͍Α͏ʹอޢ͢Δ https://developer.android.com/topic/architecture#single-source-of-truth
  14. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦Λ ࢀর Single

    Source of Truth(SSoT)ͱ͸ 6 • Guide to app architectureͷCommon architectural principlesͷҰͭ • ϙΠϯτ ◦ SSoT͸σʔλͷΦʔφʔ ◦ SSoT͚͕ͩσʔλΛมߋͰ͖Δ ◦ SSoT͸ImmutableͳσʔλΛެ։͢Δ ◦ SSoT͸֎͔ΒσʔλΛมߋՄೳͳؔ਺͔ ΠϕϯτΛड͚औΔ • σʔλͷूத؅ཧ • ଞͷͱ͜Ζ͔Βσʔλ͕มߋ͞Εͳ ͍Α͏ʹอޢ͢Δ • σʔλ΁ͷมߋ͕௥੻ՄೳʹͳΔ https://developer.android.com/topic/architecture#single-source-of-truth
  15. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺1 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    7 fun playIfNeeded() { if(!isResumed) return if(!player.isPlaying()) return if(!viewModel.isAuthorized()) { // Ͳ͔͜Ͱ·ͨplayIfNeededΛ // ݺͿඞཁ͕͋Δ requestAuthorize() return } if(!viewModel.isUserPaused()) { return } player.play() } ΠϕϯτϕʔεͰಈ͘ίʔυͷྫ
  16. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺1 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    7 • ΠϕϯτϕʔεͰίʔυ͕ಈ͘Α͏ʹͳ͍ͬͯͯSSoT͕ͳ͔ͬͨ fun playIfNeeded() { if(!isResumed) return if(!player.isPlaying()) return if(!viewModel.isAuthorized()) { // Ͳ͔͜Ͱ·ͨplayIfNeededΛ // ݺͿඞཁ͕͋Δ requestAuthorize() return } if(!viewModel.isUserPaused()) { return } player.play() } ΠϕϯτϕʔεͰಈ͘ίʔυͷྫ
  17. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺1 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    7 • ΠϕϯτϕʔεͰίʔυ͕ಈ͘Α͏ʹͳ͍ͬͯͯSSoT͕ͳ͔ͬͨ ◦ ྫ͑͹ɺplayIfNeeded()ΛݺΜͩΒதͰ࠶ੜՄೳ͔Λ൑ఆͯ͠࠶ ੜ͢ΔͳͲɻ fun playIfNeeded() { if(!isResumed) return if(!player.isPlaying()) return if(!viewModel.isAuthorized()) { // Ͳ͔͜Ͱ·ͨplayIfNeededΛ // ݺͿඞཁ͕͋Δ requestAuthorize() return } if(!viewModel.isUserPaused()) { return } player.play() } ΠϕϯτϕʔεͰಈ͘ίʔυͷྫ
  18. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺1 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    7 • ΠϕϯτϕʔεͰίʔυ͕ಈ͘Α͏ʹͳ͍ͬͯͯSSoT͕ͳ͔ͬͨ ◦ ྫ͑͹ɺplayIfNeeded()ΛݺΜͩΒதͰ࠶ੜՄೳ͔Λ൑ఆͯ͠࠶ ੜ͢ΔͳͲɻ ◦ ద੾ͳλΠϛϯάͰ͜ΕΛݺ͹ͳ͍ͱ࠶ੜ͞Εͳ͔ͬͨΓ͢ Δɻ fun playIfNeeded() { if(!isResumed) return if(!player.isPlaying()) return if(!viewModel.isAuthorized()) { // Ͳ͔͜Ͱ·ͨplayIfNeededΛ // ݺͿඞཁ͕͋Δ requestAuthorize() return } if(!viewModel.isUserPaused()) { return } player.play() } ΠϕϯτϕʔεͰಈ͘ίʔυͷྫ
  19. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺1 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    7 • ΠϕϯτϕʔεͰίʔυ͕ಈ͘Α͏ʹͳ͍ͬͯͯSSoT͕ͳ͔ͬͨ ◦ ྫ͑͹ɺplayIfNeeded()ΛݺΜͩΒதͰ࠶ੜՄೳ͔Λ൑ఆͯ͠࠶ ੜ͢ΔͳͲɻ ◦ ద੾ͳλΠϛϯάͰ͜ΕΛݺ͹ͳ͍ͱ࠶ੜ͞Εͳ͔ͬͨΓ͢ Δɻ ◦ ϓϨΠϠʔͷঢ়ଶͷSSoT͸Ͳ͜ͳͷ͔ෆ໌ͩͬͨɻViewʁ PlayerʁViewModelʁ fun playIfNeeded() { if(!isResumed) return if(!player.isPlaying()) return if(!viewModel.isAuthorized()) { // Ͳ͔͜Ͱ·ͨplayIfNeededΛ // ݺͿඞཁ͕͋Δ requestAuthorize() return } if(!viewModel.isUserPaused()) { return } player.play() } ΠϕϯτϕʔεͰಈ͘ίʔυͷྫ
  20. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺2 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    8 ViewModel಺Ͱ੔߹ੑΛऔΔͷ͕೉͍͠ྫ class ViewModel { val shouldPlay = MutableStateFlow(false) val authorized = MutableStateFlow(false) val isResumed = MutableStateFlow(false) private fun authorized() { authorized.value = true if (isResumed.value) { shouldPlay.value = true } } fun onResume() { isResumed.value = true if (authorized.value) { shouldPlay.value = true } } } ※UI states: single stream or multiple streams? https://developer.android.com/topic/architecture/ui-layer
  21. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺2 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    8 • ੔߹ੑ ViewModel಺Ͱ੔߹ੑΛऔΔͷ͕೉͍͠ྫ class ViewModel { val shouldPlay = MutableStateFlow(false) val authorized = MutableStateFlow(false) val isResumed = MutableStateFlow(false) private fun authorized() { authorized.value = true if (isResumed.value) { shouldPlay.value = true } } fun onResume() { isResumed.value = true if (authorized.value) { shouldPlay.value = true } } } ※UI states: single stream or multiple streams? https://developer.android.com/topic/architecture/ui-layer
  22. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺2 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    8 • ੔߹ੑ ◦ ViewModelͷ͍ΖΜͳؔ਺͔Β͍Ζ͍Ζͳঢ়ଶΛ ͍͡ΔͷͰ੔߹ੑΛऔΔͷ͕೉͔ͬͨ͠ɻ ViewModel಺Ͱ੔߹ੑΛऔΔͷ͕೉͍͠ྫ class ViewModel { val shouldPlay = MutableStateFlow(false) val authorized = MutableStateFlow(false) val isResumed = MutableStateFlow(false) private fun authorized() { authorized.value = true if (isResumed.value) { shouldPlay.value = true } } fun onResume() { isResumed.value = true if (authorized.value) { shouldPlay.value = true } } } ※UI states: single stream or multiple streams? https://developer.android.com/topic/architecture/ui-layer
  23. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺2 Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    8 • ੔߹ੑ ◦ ViewModelͷ͍ΖΜͳؔ਺͔Β͍Ζ͍Ζͳঢ়ଶΛ ͍͡ΔͷͰ੔߹ੑΛऔΔͷ͕೉͔ͬͨ͠ɻ ◦ ύϑΥʔϚϯεͳͲͷӨڹ͕͋·Γͳͦ͞͏ͳͱ ͜ΖͰɺؔ࿈͋Δ΋ͷʹؔͯ͠͸੔߹ੑͷ؍఺͔ ΒUiModel͸Ͱ͖Δ͚ͩҰ͕͍͍ͭ (※) ViewModel಺Ͱ੔߹ੑΛऔΔͷ͕೉͍͠ྫ class ViewModel { val shouldPlay = MutableStateFlow(false) val authorized = MutableStateFlow(false) val isResumed = MutableStateFlow(false) private fun authorized() { authorized.value = true if (isResumed.value) { shouldPlay.value = true } } fun onResume() { isResumed.value = true if (authorized.value) { shouldPlay.value = true } } } ※UI states: single stream or multiple streams? https://developer.android.com/topic/architecture/ui-layer
  24. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    9 • buildUiModel(stateFlow1, stateFlow2, block: (T1, T2) -> R): StateFlow<R> ͱ͍͏ 
 ؔ਺Λఆٛͨ͠ɻ
  25. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    9 • buildUiModel(stateFlow1, stateFlow2, block: (T1, T2) -> R): StateFlow<R> ͱ͍͏ 
 ؔ਺Λఆٛͨ͠ɻ ◦ StateFlowΛ࡞Δؔ਺ɻ
  26. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    9 • buildUiModel(stateFlow1, stateFlow2, block: (T1, T2) -> R): StateFlow<R> ͱ͍͏ 
 ؔ਺Λఆٛͨ͠ɻ ◦ StateFlowΛ࡞Δؔ਺ɻ ◦ ViewModelͷதͰ࢖͏ɻ
  27. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    9 • buildUiModel(stateFlow1, stateFlow2, block: (T1, T2) -> R): StateFlow<R> ͱ͍͏ 
 ؔ਺Λఆٛͨ͠ɻ ◦ StateFlowΛ࡞Δؔ਺ɻ ◦ ViewModelͷதͰ࢖͏ɻ ◦ த਎͸جຊతʹ͸Flowͷcombine()͕ͩɺॳظ஋ΛଞͷStateFlow͔ΒblockΛݺͼ ग़ͯ͠ɺܭࢉ͢Δ৔ॴΛҰͭʹͰ͖ΔΑ͏ʹͨ͠ɻ
  28. AbemaTV, Inc. All Rights Reserved buildUiModel()ͷఆٛ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    10 fun <T1, T2, T3, R> ViewModel.buildUiModel( flow: StateFlow<T1>, flow2: StateFlow<T2>, flow3: StateFlow<T3>, transform: (T1, T2, T3) -> R, ): StateFlow<R> = combine( flow = flow, flow2 = flow2, flow3 = flow3, transform = transform ).stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = transform( flow.value, flow2.value, flow3.value ) )
  29. AbemaTV, Inc. All Rights Reserved buildUiModel()ͷఆٛ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    10 fun <T1, T2, T3, R> ViewModel.buildUiModel( flow: StateFlow<T1>, flow2: StateFlow<T2>, flow3: StateFlow<T3>, transform: (T1, T2, T3) -> R, ): StateFlow<R> = combine( flow = flow, flow2 = flow2, flow3 = flow3, transform = transform ).stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = transform( flow.value, flow2.value, flow3.value ) ) ͜͜Ͱॳظ஋Λܭࢉͯ͠͠·͏ Ҿ਺͕͢΂ͯStateFlowͳͷͰɺ͜Ε ͕ՄೳʹͳΔɻ
  30. AbemaTV, Inc. All Rights Reserved private val productPlaybackStateFlow: StateFlow<ProductPlaybackState> =

    buildUiModel( isScreenVisibleStateFlow, isUserPausedStateFlow, isAuthorizedStateFlow ) { screenVisible, userPaused, authorized -> if(!authorized) { ProductPlaybackState.ShouldStop }else if (userPaused || !screenVisible) { ProductPlaybackState.ShouldPause } else { ProductPlaybackState.ShouldPlay } } ྫ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ 11
  31. AbemaTV, Inc. All Rights Reserved private val productPlaybackStateFlow: StateFlow<ProductPlaybackState> =

    buildUiModel( isScreenVisibleStateFlow, isUserPausedStateFlow, isAuthorizedStateFlow ) { screenVisible, userPaused, authorized -> if(!authorized) { ProductPlaybackState.ShouldStop }else if (userPaused || !screenVisible) { ProductPlaybackState.ShouldPause } else { ProductPlaybackState.ShouldPlay } } ྫ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ 11 immutableͳσʔλΛ 
 ࣋ͭStateFlow
  32. AbemaTV, Inc. All Rights Reserved private val productPlaybackStateFlow: StateFlow<ProductPlaybackState> =

    buildUiModel( isScreenVisibleStateFlow, isUserPausedStateFlow, isAuthorizedStateFlow ) { screenVisible, userPaused, authorized -> if(!authorized) { ProductPlaybackState.ShouldStop }else if (userPaused || !screenVisible) { ProductPlaybackState.ShouldPause } else { ProductPlaybackState.ShouldPlay } } ྫ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ 11 buildUiModel() immutableͳσʔλΛ 
 ࣋ͭStateFlow
  33. AbemaTV, Inc. All Rights Reserved private val productPlaybackStateFlow: StateFlow<ProductPlaybackState> =

    buildUiModel( isScreenVisibleStateFlow, isUserPausedStateFlow, isAuthorizedStateFlow ) { screenVisible, userPaused, authorized -> if(!authorized) { ProductPlaybackState.ShouldStop }else if (userPaused || !screenVisible) { ProductPlaybackState.ShouldPause } else { ProductPlaybackState.ShouldPlay } } ྫ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ 11 buildUiModel() immutableͳσʔλΛ 
 ࣋ͭStateFlow ଞͷStateFlowΛҾ਺ʹऔΔͷͰ ωετͯ͠ఆٛ΋Ͱ͖Δ
  34. AbemaTV, Inc. All Rights Reserved private val productPlaybackStateFlow: StateFlow<ProductPlaybackState> =

    buildUiModel( isScreenVisibleStateFlow, isUserPausedStateFlow, isAuthorizedStateFlow ) { screenVisible, userPaused, authorized -> if(!authorized) { ProductPlaybackState.ShouldStop }else if (userPaused || !screenVisible) { ProductPlaybackState.ShouldPause } else { ProductPlaybackState.ShouldPlay } } ྫ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ 11 buildUiModel() ॳظ஋͕ϩδοΫʹΑͬͯܭࢉ͞ ΕΔͨΊɺࢦఆ͢Δඞཁ͕ͳ͘ɺ ͜ͷϩδοΫ͕஋Λ࡞ΔSSoTʹ ͳ͍ͬͯΔ immutableͳσʔλΛ 
 ࣋ͭStateFlow ଞͷStateFlowΛҾ਺ʹऔΔͷͰ ωετͯ͠ఆٛ΋Ͱ͖Δ
  35. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ
  36. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ
  37. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ ◦ ͋Δσʔλʹର͢ΔҰ؏ੑ͸อͪ΍͍͕͢ɺྫ͑͹API͔ΒԿ͔Λऔಘͨ͋͠ͱʹԿΛॻ͖׵͑Δͷ͔ɺͲ͏Ξϓ ϦέʔγϣϯʹӨڹΛ༩͑Δͷ͔ͷॲཧ͸ٯʹ௥͍ʹ͘͘ͳΔ͔΋͠Εͳ͍ɻ
  38. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ ◦ ͋Δσʔλʹର͢ΔҰ؏ੑ͸อͪ΍͍͕͢ɺྫ͑͹API͔ΒԿ͔Λऔಘͨ͋͠ͱʹԿΛॻ͖׵͑Δͷ͔ɺͲ͏Ξϓ ϦέʔγϣϯʹӨڹΛ༩͑Δͷ͔ͷॲཧ͸ٯʹ௥͍ʹ͘͘ͳΔ͔΋͠Εͳ͍ɻ ▪ ͜Ε͸τϨʔυΦϑͱͯ͠ड͚ೖΕ͍ͯΔɻ
  39. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ ◦ ͋Δσʔλʹର͢ΔҰ؏ੑ͸อͪ΍͍͕͢ɺྫ͑͹API͔ΒԿ͔Λऔಘͨ͋͠ͱʹԿΛॻ͖׵͑Δͷ͔ɺͲ͏Ξϓ ϦέʔγϣϯʹӨڹΛ༩͑Δͷ͔ͷॲཧ͸ٯʹ௥͍ʹ͘͘ͳΔ͔΋͠Εͳ͍ɻ ▪ ͜Ε͸τϨʔυΦϑͱͯ͠ड͚ೖΕ͍ͯΔɻ • ୅ସखஈ
  40. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ ◦ ͋Δσʔλʹର͢ΔҰ؏ੑ͸อͪ΍͍͕͢ɺྫ͑͹API͔ΒԿ͔Λऔಘͨ͋͠ͱʹԿΛॻ͖׵͑Δͷ͔ɺͲ͏Ξϓ ϦέʔγϣϯʹӨڹΛ༩͑Δͷ͔ͷॲཧ͸ٯʹ௥͍ʹ͘͘ͳΔ͔΋͠Εͳ͍ɻ ▪ ͜Ε͸τϨʔυΦϑͱͯ͠ड͚ೖΕ͍ͯΔɻ • ୅ସखஈ ◦ Cash AppͷMoleculeͰ΋ྑ͔ͬͨɻ
  41. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ ◦ ͋Δσʔλʹର͢ΔҰ؏ੑ͸อͪ΍͍͕͢ɺྫ͑͹API͔ΒԿ͔Λऔಘͨ͋͠ͱʹԿΛॻ͖׵͑Δͷ͔ɺͲ͏Ξϓ ϦέʔγϣϯʹӨڹΛ༩͑Δͷ͔ͷॲཧ͸ٯʹ௥͍ʹ͘͘ͳΔ͔΋͠Εͳ͍ɻ ▪ ͜Ε͸τϨʔυΦϑͱͯ͠ड͚ೖΕ͍ͯΔɻ • ୅ସखஈ ◦ Cash AppͷMoleculeͰ΋ྑ͔ͬͨɻ ▪ ͜ΕΛ࢖͏ͱJetpack ComposeΛ࢖ͬͯStateFlowΛ࡞ΕΔɻ
  42. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Single Source of Truth(SSoT)ΛͲ͏࢖͔ͬͨ

    12 • τϨʔυΦϑ ◦ buildUiModel()ͷҾ਺͕ଟ͘ͳΓ͗͢Δɺͭ·Γ͏·͘෼཭Ͱ͖͍ͯͳ͍ύλʔϯ͕͋ͬͨɻ ▪ 9ݸҎ্͸૿΍͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺωετͯ͠͏·͘࡞ΕΔΑ͏ʹͨ͠ɻ ◦ ͋Δσʔλʹର͢ΔҰ؏ੑ͸อͪ΍͍͕͢ɺྫ͑͹API͔ΒԿ͔Λऔಘͨ͋͠ͱʹԿΛॻ͖׵͑Δͷ͔ɺͲ͏Ξϓ ϦέʔγϣϯʹӨڹΛ༩͑Δͷ͔ͷॲཧ͸ٯʹ௥͍ʹ͘͘ͳΔ͔΋͠Εͳ͍ɻ ▪ ͜Ε͸τϨʔυΦϑͱͯ͠ड͚ೖΕ͍ͯΔɻ • ୅ସखஈ ◦ Cash AppͷMoleculeͰ΋ྑ͔ͬͨɻ ▪ ͜ΕΛ࢖͏ͱJetpack ComposeΛ࢖ͬͯStateFlowΛ࡞ΕΔɻ ▪ ͦΕͧΕͷίϯϙʔωϯτͷUiModelͷ࡞੒ͳͲΛɺؔ਺ʹ੾Γग़͍͕ͨ͠ɺͦ͏ͯ͠େ͖͘ͳ͍ͬͯͬ ͨͱ͖ʹύϑΥʔϚϯε͕Ͳ͏ͳͷ͔ͳͲɺݒ೦͕͋Γ࠾༻Ͱ͖ͳ͔ͬͨɻ(ѱ͍Θ͚Ͱ͸ͳ͘ଌΕ͍ͯ ͳ͍͚ͩͰ͢ɻ)
  43. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦ Λࢀর Unidirectional

    Data Flow(UDF)ͱ͸ 14 https://developer.android.com/topic/architecture#unidirectional-data-flow
  44. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦ Λࢀর Unidirectional

    Data Flow(UDF)ͱ͸ 14 • Guide to App Architectureͷ Common architectural principlesͷҰ ͭ https://developer.android.com/topic/architecture#unidirectional-data-flow
  45. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦ Λࢀর Unidirectional

    Data Flow(UDF)ͱ͸ 14 • Guide to App Architectureͷ Common architectural principlesͷҰ ͭ • ϙΠϯτ https://developer.android.com/topic/architecture#unidirectional-data-flow
  46. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦ Λࢀর Unidirectional

    Data Flow(UDF)ͱ͸ 14 • Guide to App Architectureͷ Common architectural principlesͷҰ ͭ • ϙΠϯτ ◦ State͕Ұํ޲ʹྲྀΕɺ 
 Event͕ٯଆʹྲྀΕΔɻ https://developer.android.com/topic/architecture#unidirectional-data-flow
  47. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦ Λࢀর Unidirectional

    Data Flow(UDF)ͱ͸ 14 • Guide to App Architectureͷ Common architectural principlesͷҰ ͭ • ϙΠϯτ ◦ State͕Ұํ޲ʹྲྀΕɺ 
 Event͕ٯଆʹྲྀΕΔɻ • SSoTʹΑͬͯಘΒΕΔརӹɻ https://developer.android.com/topic/architecture#unidirectional-data-flow
  48. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersʹ͋Δઃܭ֓೦ Λࢀর Unidirectional

    Data Flow(UDF)ͱ͸ 14 • Guide to App Architectureͷ Common architectural principlesͷҰ ͭ • ϙΠϯτ ◦ State͕Ұํ޲ʹྲྀΕɺ 
 Event͕ٯଆʹྲྀΕΔɻ • SSoTʹΑͬͯಘΒΕΔརӹɻ • Ұ؏ੑɺޡΓΛݮΒ͢ɺσόοάΛָ ʹ͢Δ https://developer.android.com/topic/architecture#unidirectional-data-flow
  49. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 15

    • Fragment΍RecyclerViewͷΞΠςϜ·Ͱɺ͞ ·͟·ͳͱ͜Ζʹಉ͡ViewModel͕Ͱͯ͘Δ࣮ ૷ʹͳ͍ͬͯͯσʔλͷྲྀΕ͕౷Ұ͞Ε͍ͯͳ ͔ͬͨɻ͜ΕʹΑΓɺσόοάͳͲ͕೉͘͠ͳ ΔͳͲ͕ൃੜ͍ͯͨ͠ɻ
  50. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 16

    • ৽͍͠Jetpack Compose ͔ ࣮੷ͷAndroid View ͔ɺͲͪΒΛ࢖͏͔ɻ
  51. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 16

    • ৽͍͠Jetpack Compose ͔ ࣮੷ͷAndroid View ͔ɺͲͪΒΛ࢖͏͔ɻ ◦ ΋͠ऴ൫ʹக໋తͳ໰୊͕ݟ͔ͭΔͱऔΓฦ͕͔ͭ͠ͳ͔ͬͨͷͰɺ 
 දࣔͷج൫ͱͳΔ෦෼ʹ͸ΞϓϦ಺Ͱ࣮੷ͷ͋ΔAndroid ViewΛ࢖͍ͨ ͔ͬͨɻ
  52. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 16

    • ৽͍͠Jetpack Compose ͔ ࣮੷ͷAndroid View ͔ɺͲͪΒΛ࢖͏͔ɻ ◦ ΋͠ऴ൫ʹக໋తͳ໰୊͕ݟ͔ͭΔͱऔΓฦ͕͔ͭ͠ͳ͔ͬͨͷͰɺ 
 දࣔͷج൫ͱͳΔ෦෼ʹ͸ΞϓϦ಺Ͱ࣮੷ͷ͋ΔAndroid ViewΛ࢖͍ͨ ͔ͬͨɻ ◦ ࠓޙͷJetpack Composeͱͷޓ׵ੑΛͲ͏୲อ͢Δ͔ʁ
  53. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 17

    class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } }
  54. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 17

    • ViewBinderͱ͍͏ΫϥεΛఆٛͨ͠ɻ class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } }
  55. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 17

    • ViewBinderͱ͍͏ΫϥεΛఆٛͨ͠ɻ ◦ ͜ͷΫϥεʹ͸Ҿ਺ʹViewModelͳͲ Λ౉͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺView ͸جຊతʹ͸͜ͷதͰมߋΛՃ͑Δɻ class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } }
  56. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 17

    • ViewBinderͱ͍͏ΫϥεΛఆٛͨ͠ɻ ◦ ͜ͷΫϥεʹ͸Ҿ਺ʹViewModelͳͲ Λ౉͞ͳ͍ͱ͍͏੍໿Λ͚ͭͯɺView ͸جຊతʹ͸͜ͷதͰมߋΛՃ͑Δɻ ◦ Jetpack ComposeʹҠߦ͠ऴΘΕ͹ফ ͤΔΫϥεɻ class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } }
  57. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 18

    class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } } ͨͩͷγϯϓϧͳΫϥε
  58. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 18

    class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } } ݺͼݩ͔Βσʔλ͕౉͞ΕΔ buildUiModel()ʹΑͬͯ౷߹͞Εͨ UiModel͕౉ͬͯ͘ΔͷͰɺ apply()ؔ਺͸ҰͭͷΈʹͳΓγϯϓϧ ͨͩͷγϯϓϧͳΫϥε
  59. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 18

    class ViewBinder( private val binding: PlayerBinding, onUserPlayerPaused: () -> Unit ) { fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title binding.pauseButton.setOnClickListener { onUserPlayerPaused() } } } ݺͼݩ͔Βσʔλ͕౉͞ΕΔ ݺͼݩʹΠϕϯτΛ౉͢ buildUiModel()ʹΑͬͯ౷߹͞Εͨ UiModel͕౉ͬͯ͘ΔͷͰɺ apply()ؔ਺͸ҰͭͷΈʹͳΓγϯϓϧ ͨͩͷγϯϓϧͳΫϥε
  60. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ
  61. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ ◦ ViewBinderΫϥεͷҾ਺͕Πϕϯτͷ਺͚ͩͰ͖ΔͷͰଟ͘ͳΓ͕ͪ
  62. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ ◦ ViewBinderΫϥεͷҾ਺͕Πϕϯτͷ਺͚ͩͰ͖ΔͷͰଟ͘ͳΓ͕ͪ ▪ → ໊લ෇͖Ҿ਺ʹͳΓɺ௥͍΍͍͢ͷͰɺͦ͜·Ͱͷ໰୊ʹ͸ͳΒͳ͍͸ͣɻ
  63. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ ◦ ViewBinderΫϥεͷҾ਺͕Πϕϯτͷ਺͚ͩͰ͖ΔͷͰଟ͘ͳΓ͕ͪ ▪ → ໊લ෇͖Ҿ਺ʹͳΓɺ௥͍΍͍͢ͷͰɺͦ͜·Ͱͷ໰୊ʹ͸ͳΒͳ͍͸ͣɻ ◦ ViewBinderͷதʹ͸View͚ͩͰͳ͘Compose΋ೖ͍ͬͯͨͷͰɺComposeͰ͸஋Λ౉͢ͱ͖ʹStateFlowΛ౉͢ඞཁ͕͋ͬ ͕ͨStateFlow΋ViewBinderʹ౉͢ඞཁ͕͋ͬͨͷͰɺͦΕʹΑͬͯదԠํ๏͕෼ࢄͯ͠͠·͍ͬͯͨɻ
  64. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ ◦ ViewBinderΫϥεͷҾ਺͕Πϕϯτͷ਺͚ͩͰ͖ΔͷͰଟ͘ͳΓ͕ͪ ▪ → ໊લ෇͖Ҿ਺ʹͳΓɺ௥͍΍͍͢ͷͰɺͦ͜·Ͱͷ໰୊ʹ͸ͳΒͳ͍͸ͣɻ ◦ ViewBinderͷதʹ͸View͚ͩͰͳ͘Compose΋ೖ͍ͬͯͨͷͰɺComposeͰ͸஋Λ౉͢ͱ͖ʹStateFlowΛ౉͢ඞཁ͕͋ͬ ͕ͨStateFlow΋ViewBinderʹ౉͢ඞཁ͕͋ͬͨͷͰɺͦΕʹΑͬͯదԠํ๏͕෼ࢄͯ͠͠·͍ͬͯͨɻ ▪ → ͏·͘StateFlowͷΈͰಈ͔͢Α͏ʹ͢ΔͳͲͷ࢓૊Έ͕ඞཁɻ
  65. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ ◦ ViewBinderΫϥεͷҾ਺͕Πϕϯτͷ਺͚ͩͰ͖ΔͷͰଟ͘ͳΓ͕ͪ ▪ → ໊લ෇͖Ҿ਺ʹͳΓɺ௥͍΍͍͢ͷͰɺͦ͜·Ͱͷ໰୊ʹ͸ͳΒͳ͍͸ͣɻ ◦ ViewBinderͷதʹ͸View͚ͩͰͳ͘Compose΋ೖ͍ͬͯͨͷͰɺComposeͰ͸஋Λ౉͢ͱ͖ʹStateFlowΛ౉͢ඞཁ͕͋ͬ ͕ͨStateFlow΋ViewBinderʹ౉͢ඞཁ͕͋ͬͨͷͰɺͦΕʹΑͬͯదԠํ๏͕෼ࢄͯ͠͠·͍ͬͯͨɻ ▪ → ͏·͘StateFlowͷΈͰಈ͔͢Α͏ʹ͢ΔͳͲͷ࢓૊Έ͕ඞཁɻ ◦ ࡉ͔͍஋ͷมԽͰ͢΂ͯΛViewBinder.apply()ͰViewʹదԠ͠௚͢ͷͰύϑΥʔϚϯεతʹ·ͣͦ͏Ͱ͸ʁ
  66. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 20

    • τϨʔυΦϑ ◦ ViewBinderͱ͍͏ଘࡏࣗମ͕ೃછΈ͕ͳ͍ͷͰֶशίετ্͕͕Δɻ ◦ ViewBinderΫϥεͷҾ਺͕Πϕϯτͷ਺͚ͩͰ͖ΔͷͰଟ͘ͳΓ͕ͪ ▪ → ໊લ෇͖Ҿ਺ʹͳΓɺ௥͍΍͍͢ͷͰɺͦ͜·Ͱͷ໰୊ʹ͸ͳΒͳ͍͸ͣɻ ◦ ViewBinderͷதʹ͸View͚ͩͰͳ͘Compose΋ೖ͍ͬͯͨͷͰɺComposeͰ͸஋Λ౉͢ͱ͖ʹStateFlowΛ౉͢ඞཁ͕͋ͬ ͕ͨStateFlow΋ViewBinderʹ౉͢ඞཁ͕͋ͬͨͷͰɺͦΕʹΑͬͯదԠํ๏͕෼ࢄͯ͠͠·͍ͬͯͨɻ ▪ → ͏·͘StateFlowͷΈͰಈ͔͢Α͏ʹ͢ΔͳͲͷ࢓૊Έ͕ඞཁɻ ◦ ࡉ͔͍஋ͷมԽͰ͢΂ͯΛViewBinder.apply()ͰViewʹదԠ͠௚͢ͷͰύϑΥʔϚϯεతʹ·ͣͦ͏Ͱ͸ʁ ▪ → (࣍ϖʔδ)
  67. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεతʹ·ͣͦ͏ͱ͍͏࿩ Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ 21

    ViewBinder.apply()Λେ͖ͳ୯ҐͰ૸ΒͤΔͱɺ 
 ྫ͑͹TextView͕தʹ͋Δͱɺ 
 wrap_contentͷ৔߹͸ಉ͡ςΩετͰ΋ requestLayout()͕૸ΓશମΛϨΠΞ΢τ͠௚͢ → ԿΒ͔ͷࠩ෼ߋ৽͕ඞཁͦ͏ fun apply(uiModel: PlayerUiModel) { binding.title.text = uiModel.title … } textView.setText()͔ΒrequestLayout()͕ݺ͹Ε͍ͯΔྫ
  68. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεʹؔͯ͠ɺJetpack Compose͕಺෦తʹߦ͍ͬͯΔΞϓϩʔν Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ

    22 • Jetpack ComposeͰ͸ঢ়ଶΛComposableؔ਺ຖʹ͍࣋ͬͯͯ(SlotTable)ɺؔ਺ͷҾ਺͕มΘͬͨ࣌ͷΈؔ਺தͷॲཧΛ ߦ͏ɻ(͍ΘΏΔdonut-hole skipping)
  69. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεʹؔͯ͠ɺJetpack Compose͕಺෦తʹߦ͍ͬͯΔΞϓϩʔν Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ

    22 • Jetpack ComposeͰ͸ঢ়ଶΛComposableؔ਺ຖʹ͍࣋ͬͯͯ(SlotTable)ɺؔ਺ͷҾ਺͕มΘͬͨ࣌ͷΈؔ਺தͷॲཧΛ ߦ͏ɻ(͍ΘΏΔdonut-hole skipping) inline fun <reified T> View.updateTag( @IdRes tagId: Int, value: T, onUpdate: (old: T?, new: T) -> Unit ) { val old: T? = getTag(tagId) as? T if (old != value) { setTag(tagId, value) onUpdate(old, value) } }
  70. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεʹؔͯ͠ɺJetpack Compose͕಺෦తʹߦ͍ͬͯΔΞϓϩʔν Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ

    22 • Jetpack ComposeͰ͸ঢ়ଶΛComposableؔ਺ຖʹ͍࣋ͬͯͯ(SlotTable)ɺؔ਺ͷҾ਺͕มΘͬͨ࣌ͷΈؔ਺தͷॲཧΛ ߦ͏ɻ(͍ΘΏΔdonut-hole skipping) inline fun <reified T> View.updateTag( @IdRes tagId: Int, value: T, onUpdate: (old: T?, new: T) -> Unit ) { val old: T? = getTag(tagId) as? T if (old != value) { setTag(tagId, value) onUpdate(old, value) } } • ͜ΕΛٖࣅతʹViewͰ΋Ͱ͖ͳ͍͔ʁ
  71. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεʹؔͯ͠ɺJetpack Compose͕಺෦తʹߦ͍ͬͯΔΞϓϩʔν Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ

    22 • Jetpack ComposeͰ͸ঢ়ଶΛComposableؔ਺ຖʹ͍࣋ͬͯͯ(SlotTable)ɺؔ਺ͷҾ਺͕มΘͬͨ࣌ͷΈؔ਺தͷॲཧΛ ߦ͏ɻ(͍ΘΏΔdonut-hole skipping) inline fun <reified T> View.updateTag( @IdRes tagId: Int, value: T, onUpdate: (old: T?, new: T) -> Unit ) { val old: T? = getTag(tagId) as? T if (old != value) { setTag(tagId, value) onUpdate(old, value) } } • ͜ΕΛٖࣅతʹViewͰ΋Ͱ͖ͳ͍͔ʁ ◦ View.updateTag { }ͱ͍͏ؔ਺Λ࡞੒
  72. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεʹؔͯ͠ɺJetpack Compose͕಺෦తʹߦ͍ͬͯΔΞϓϩʔν Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ

    22 • Jetpack ComposeͰ͸ঢ়ଶΛComposableؔ਺ຖʹ͍࣋ͬͯͯ(SlotTable)ɺؔ਺ͷҾ਺͕มΘͬͨ࣌ͷΈؔ਺தͷॲཧΛ ߦ͏ɻ(͍ΘΏΔdonut-hole skipping) inline fun <reified T> View.updateTag( @IdRes tagId: Int, value: T, onUpdate: (old: T?, new: T) -> Unit ) { val old: T? = getTag(tagId) as? T if (old != value) { setTag(tagId, value) onUpdate(old, value) } } • ͜ΕΛٖࣅతʹViewͰ΋Ͱ͖ͳ͍͔ʁ ◦ View.updateTag { }ͱ͍͏ؔ਺Λ࡞੒ ▪ Viewͷอ࣋͢ΔtagΛར༻ͯ͠ 
 ࠩ෼͕͋Ε͹౉͞Εͨؔ਺ΛݺͿ
  73. AbemaTV, Inc. All Rights Reserved ύϑΥʔϚϯεʹؔͯ͠ɺJetpack Compose͕಺෦తʹߦ͍ͬͯΔΞϓϩʔν Unidirectional Data Flow(UDF)ΛͲ͏࢖͔ͬͨ

    22 • Jetpack ComposeͰ͸ঢ়ଶΛComposableؔ਺ຖʹ͍࣋ͬͯͯ(SlotTable)ɺؔ਺ͷҾ਺͕มΘͬͨ࣌ͷΈؔ਺தͷॲཧΛ ߦ͏ɻ(͍ΘΏΔdonut-hole skipping) inline fun <reified T> View.updateTag( @IdRes tagId: Int, value: T, onUpdate: (old: T?, new: T) -> Unit ) { val old: T? = getTag(tagId) as? T if (old != value) { setTag(tagId, value) onUpdate(old, value) } } • ͜ΕΛٖࣅతʹViewͰ΋Ͱ͖ͳ͍͔ʁ ◦ View.updateTag { }ͱ͍͏ؔ਺Λ࡞੒ ▪ Viewͷอ࣋͢ΔtagΛར༻ͯ͠ 
 ࠩ෼͕͋Ε͹౉͞Εͨؔ਺ΛݺͿ ▪ ͜ΕΛViewBinder.apply()ͷதͰɺ 
 ར༻͍ͯ͘͜͠ͱͰղܾɻ
  74. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersͷ Recommendations for

    Android architectureΑΓ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ͱ͸ 24 https://developer.android.com/topic/architecture/ recommendations?hl=en
  75. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersͷ Recommendations for

    Android architectureΑΓ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ͱ͸ 24 • UI layerʹ͋Δ߲໨ͰɺStrongly recommended https://developer.android.com/topic/architecture/ recommendations?hl=en
  76. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersͷ Recommendations for

    Android architectureΑΓ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ͱ͸ 24 • UI layerʹ͋Δ߲໨ͰɺStrongly recommended • ϙΠϯτ https://developer.android.com/topic/architecture/ recommendations?hl=en
  77. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersͷ Recommendations for

    Android architectureΑΓ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ͱ͸ 24 • UI layerʹ͋Δ߲໨ͰɺStrongly recommended • ϙΠϯτ ◦ UIͷΞΫγϣϯ͸ΠϕϯτͰ͸ͳ ͘ɺUI StateͷΞοϓσʔτʹ ΑͬͯߦΘΕΔ΂͖ɻ https://developer.android.com/topic/architecture/ recommendations?hl=en
  78. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersͷ Recommendations for

    Android architectureΑΓ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ͱ͸ 24 • UI layerʹ͋Δ߲໨ͰɺStrongly recommended • ϙΠϯτ ◦ UIͷΞΫγϣϯ͸ΠϕϯτͰ͸ͳ ͘ɺUI StateͷΞοϓσʔτʹ ΑͬͯߦΘΕΔ΂͖ɻ • Πϕϯτ͕࠶ݱՄೳʹͳΔɻ https://developer.android.com/topic/architecture/ recommendations?hl=en
  79. AbemaTV, Inc. All Rights Reserved ϝϦοτ Android Developersͷ Recommendations for

    Android architectureΑΓ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ͱ͸ 24 • UI layerʹ͋Δ߲໨ͰɺStrongly recommended • ϙΠϯτ ◦ UIͷΞΫγϣϯ͸ΠϕϯτͰ͸ͳ ͘ɺUI StateͷΞοϓσʔτʹ ΑͬͯߦΘΕΔ΂͖ɻ • Πϕϯτ͕࠶ݱՄೳʹͳΔɻ • UIΞΫγϣϯ͕ࣦΘΕͳ͍͜ͱ͕อূ ͞ΕΔɻ https://developer.android.com/topic/architecture/ recommendations?hl=en
  80. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 25 • collect{}ͷλΠϛϯάͳͲͰɺSharedFlowΛ࢖͏ίʔυͰόά͕ى͖Δ͜ͱ͕

    ͋ͬͨɻ ◦ ྫ͑͹ɺreplay=0ͷ৔߹ɺrepeatOnLifecycle(Lifecycle.State.STARTED)ͳ ͲΛ࢖͍ͬͯͯɺΠϕϯτ͕STARTEDҎલʹૹΒΕΔͱফ͑ΔͳͲ
  81. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 25 • collect{}ͷλΠϛϯάͳͲͰɺSharedFlowΛ࢖͏ίʔυͰόά͕ى͖Δ͜ͱ͕

    ͋ͬͨɻ ◦ ྫ͑͹ɺreplay=0ͷ৔߹ɺrepeatOnLifecycle(Lifecycle.State.STARTED)ͳ ͲΛ࢖͍ͬͯͯɺΠϕϯτ͕STARTEDҎલʹૹΒΕΔͱফ͑ΔͳͲ • ·ͨɺςετͩͱFake͕ૣ͘σʔλΛฦ͢ӨڹͰɺࢠͷFragmentͰσʔλΛड ͚औΔલʹSharedFlow͕ൃՐ͢ΔͳͲςετ΋೉͍͠ύλʔϯ͕͋Δ
  82. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 27 // ViewBinder.apply()ͷத

    if (alertRequestStates.showPlaybackErrorAlertRequestState.isRequested()) { onShowPlaybackErrorAlert() showSnackbar(message) } // ViewModel val showPlaybackErrorAlertRequestState = MutableStateFlow(RequestState.NotRequested) fun hoge() { try { ... } catch(e: PlaybackError) { showPlaybackErrorAlertRequestState.value = RequestState.Requested(PlaybackErrorAlert) } } fun onShowPlaybackErrorAlert() { showPlaybackErrorAlertRequestState.value = RequestState.NotRequested }
  83. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 27 // ViewBinder.apply()ͷத

    if (alertRequestStates.showPlaybackErrorAlertRequestState.isRequested()) { onShowPlaybackErrorAlert() showSnackbar(message) } // ViewModel val showPlaybackErrorAlertRequestState = MutableStateFlow(RequestState.NotRequested) fun hoge() { try { ... } catch(e: PlaybackError) { showPlaybackErrorAlertRequestState.value = RequestState.Requested(PlaybackErrorAlert) } } fun onShowPlaybackErrorAlert() { showPlaybackErrorAlertRequestState.value = RequestState.NotRequested } ᶃ StateFlowΛఆٛ
  84. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 27 // ViewBinder.apply()ͷத

    if (alertRequestStates.showPlaybackErrorAlertRequestState.isRequested()) { onShowPlaybackErrorAlert() showSnackbar(message) } // ViewModel val showPlaybackErrorAlertRequestState = MutableStateFlow(RequestState.NotRequested) fun hoge() { try { ... } catch(e: PlaybackError) { showPlaybackErrorAlertRequestState.value = RequestState.Requested(PlaybackErrorAlert) } } fun onShowPlaybackErrorAlert() { showPlaybackErrorAlertRequestState.value = RequestState.NotRequested } ᶃ StateFlowΛఆٛ ᶄ RequestedΛηοτ
  85. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 27 // ViewBinder.apply()ͷத

    if (alertRequestStates.showPlaybackErrorAlertRequestState.isRequested()) { onShowPlaybackErrorAlert() showSnackbar(message) } // ViewModel val showPlaybackErrorAlertRequestState = MutableStateFlow(RequestState.NotRequested) fun hoge() { try { ... } catch(e: PlaybackError) { showPlaybackErrorAlertRequestState.value = RequestState.Requested(PlaybackErrorAlert) } } fun onShowPlaybackErrorAlert() { showPlaybackErrorAlertRequestState.value = RequestState.NotRequested } ᶃ StateFlowΛఆٛ ᶄ RequestedΛηοτ ᶅ Requestedʹͳͬͨ΋ͷΛ ফඅ͢Δ
  86. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 27 // ViewBinder.apply()ͷத

    if (alertRequestStates.showPlaybackErrorAlertRequestState.isRequested()) { onShowPlaybackErrorAlert() showSnackbar(message) } // ViewModel val showPlaybackErrorAlertRequestState = MutableStateFlow(RequestState.NotRequested) fun hoge() { try { ... } catch(e: PlaybackError) { showPlaybackErrorAlertRequestState.value = RequestState.Requested(PlaybackErrorAlert) } } fun onShowPlaybackErrorAlert() { showPlaybackErrorAlertRequestState.value = RequestState.NotRequested } ᶃ StateFlowΛఆٛ ᶄ RequestedΛηοτ ᶅ Requestedʹͳͬͨ΋ͷΛ ফඅ͢Δ ᶆ NotRequestedʹ໭͢
  87. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 28 • τϨʔυΦϑ

    ◦ ίʔυ͕૿͑Δมߋɻ ▪ ϘΠϥʔϓϨʔτͬΆ͍ײ͡͸͋ΔͷͰɺͲͷStateΛม͍͑ͯΔ͔ͳͲҙਤͨ͠มߋͳͷ͔Ͳ͏͔͸Ϩ Ϗϡʔ͢Δඞཁ͕͋Δɻ
  88. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 28 • τϨʔυΦϑ

    ◦ ίʔυ͕૿͑Δมߋɻ ▪ ϘΠϥʔϓϨʔτͬΆ͍ײ͡͸͋ΔͷͰɺͲͷStateΛม͍͑ͯΔ͔ͳͲҙਤͨ͠มߋͳͷ͔Ͳ͏͔͸Ϩ Ϗϡʔ͢Δඞཁ͕͋Δɻ ▪ ୅ΘΓʹϓϨΠϠʔͷStateͳͲͰ͸໌֬ʹSingle Source of Truth͕࡞ΒΕͨͨΊɺ໌Β͔ʹ؅ཧ͠΍͘͢ ͳͬͨײ͕͋͡Δɻ
  89. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 28 • τϨʔυΦϑ

    ◦ ίʔυ͕૿͑Δมߋɻ ▪ ϘΠϥʔϓϨʔτͬΆ͍ײ͡͸͋ΔͷͰɺͲͷStateΛม͍͑ͯΔ͔ͳͲҙਤͨ͠มߋͳͷ͔Ͳ͏͔͸Ϩ Ϗϡʔ͢Δඞཁ͕͋Δɻ ▪ ୅ΘΓʹϓϨΠϠʔͷStateͳͲͰ͸໌֬ʹSingle Source of Truth͕࡞ΒΕͨͨΊɺ໌Β͔ʹ؅ཧ͠΍͘͢ ͳͬͨײ͕͋͡Δɻ ▪ ίʔυͷ౷Ұײ͸Ͱ͖ͨɻComposeͷLaunchedEffect()ͳͲʹ΋ޓ׵ੑ͕͋Δ͸ͣɻ
  90. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 28 • τϨʔυΦϑ

    ◦ ίʔυ͕૿͑Δมߋɻ ▪ ϘΠϥʔϓϨʔτͬΆ͍ײ͡͸͋ΔͷͰɺͲͷStateΛม͍͑ͯΔ͔ͳͲҙਤͨ͠มߋͳͷ͔Ͳ͏͔͸Ϩ Ϗϡʔ͢Δඞཁ͕͋Δɻ ▪ ୅ΘΓʹϓϨΠϠʔͷStateͳͲͰ͸໌֬ʹSingle Source of Truth͕࡞ΒΕͨͨΊɺ໌Β͔ʹ؅ཧ͠΍͘͢ ͳͬͨײ͕͋͡Δɻ ▪ ίʔυͷ౷Ұײ͸Ͱ͖ͨɻComposeͷLaunchedEffect()ͳͲʹ΋ޓ׵ੑ͕͋Δ͸ͣɻ • ୅ସखஈ
  91. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 28 • τϨʔυΦϑ

    ◦ ίʔυ͕૿͑Δมߋɻ ▪ ϘΠϥʔϓϨʔτͬΆ͍ײ͡͸͋ΔͷͰɺͲͷStateΛม͍͑ͯΔ͔ͳͲҙਤͨ͠มߋͳͷ͔Ͳ͏͔͸Ϩ Ϗϡʔ͢Δඞཁ͕͋Δɻ ▪ ୅ΘΓʹϓϨΠϠʔͷStateͳͲͰ͸໌֬ʹSingle Source of Truth͕࡞ΒΕͨͨΊɺ໌Β͔ʹ؅ཧ͠΍͘͢ ͳͬͨײ͕͋͡Δɻ ▪ ίʔυͷ౷Ұײ͸Ͱ͖ͨɻComposeͷLaunchedEffect()ͳͲʹ΋ޓ׵ੑ͕͋Δ͸ͣɻ • ୅ସखஈ ◦ UiModelʹมߋ͢ΔͨΊͷϥϜμΛ౉͢Α͏ͳ΍Γํ΋ࢼͯ͠Έͯ΋ྑ͍͔΋͠Εͳ͍ɻ˞
  92. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍”ΛͲ͏࢖͔ͬͨ 28 • τϨʔυΦϑ

    ◦ ίʔυ͕૿͑Δมߋɻ ▪ ϘΠϥʔϓϨʔτͬΆ͍ײ͡͸͋ΔͷͰɺͲͷStateΛม͍͑ͯΔ͔ͳͲҙਤͨ͠มߋͳͷ͔Ͳ͏͔͸Ϩ Ϗϡʔ͢Δඞཁ͕͋Δɻ ▪ ୅ΘΓʹϓϨΠϠʔͷStateͳͲͰ͸໌֬ʹSingle Source of Truth͕࡞ΒΕͨͨΊɺ໌Β͔ʹ؅ཧ͠΍͘͢ ͳͬͨײ͕͋͡Δɻ ▪ ίʔυͷ౷Ұײ͸Ͱ͖ͨɻComposeͷLaunchedEffect()ͳͲʹ΋ޓ׵ੑ͕͋Δ͸ͣɻ • ୅ସखஈ ◦ UiModelʹมߋ͢ΔͨΊͷϥϜμΛ౉͢Α͏ͳ΍Γํ΋ࢼͯ͠Έͯ΋ྑ͍͔΋͠Εͳ͍ɻ˞ ※ https://developer.android.com/topic/architecture/ui-layer/events#recyclerview-events
  93. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 ϝϦοτ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  94. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ϝϦοτ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  95. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ϝϦοτ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  96. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ϝϦοτ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  97. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ◦ Mock ϝϦοτ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  98. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ◦ Mock • (ύϑΥʔϚϯεͳͲͷཁ݅ʹΑΓม ΘΔɻ) ϝϦοτ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  99. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ◦ Mock • (ύϑΥʔϚϯεͳͲͷཁ݅ʹΑΓม ΘΔɻ) ϝϦοτ • ຊ෺ͷΦϒδΣΫτ͸ຊ౰ͷ໰୊ΛΩϟον͢Δ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  100. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ◦ Mock • (ύϑΥʔϚϯεͳͲͷཁ݅ʹΑΓม ΘΔɻ) ϝϦοτ • ຊ෺ͷΦϒδΣΫτ͸ຊ౰ͷ໰୊ΛΩϟον͢Δ • ಉ͡ΧόϨοδʹରͯ͠ɺগͳ͍ςετίʔυͰྑ͘ͳ Δ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  101. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ◦ Mock • (ύϑΥʔϚϯεͳͲͷཁ݅ʹΑΓม ΘΔɻ) ϝϦοτ • ຊ෺ͷΦϒδΣΫτ͸ຊ౰ͷ໰୊ΛΩϟον͢Δ • ಉ͡ΧόϨοδʹରͯ͠ɺগͳ͍ςετίʔυͰྑ͘ͳ Δ • ςετ͕ࣦഊ͢Δͱ͖͸ຊ౰ͷ໰୊Λर͍ͬͯΔՄೳੑ ͕ߴ͘ͳΔ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  102. AbemaTV, Inc. All Rights Reserved GoogleͷDIϥΠϒϥϦ Dagger Hiltͷ υΩϡϝϯτͷHilt Testing

    PhilosophyΑΓ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ 30 • ҎԼͷॱ൪Ͱ࢖͏ ◦ ຊ෺ͷΦϒδΣΫτ ◦ ϥΠϒϥϦ͕ఏڙ͢ΔFake ◦ Mock • (ύϑΥʔϚϯεͳͲͷཁ݅ʹΑΓม ΘΔɻ) ϝϦοτ • ຊ෺ͷΦϒδΣΫτ͸ຊ౰ͷ໰୊ΛΩϟον͢Δ • ಉ͡ΧόϨοδʹରͯ͠ɺগͳ͍ςετίʔυͰྑ͘ͳ Δ • ςετ͕ࣦഊ͢Δͱ͖͸ຊ౰ͷ໰୊Λर͍ͬͯΔՄೳੑ ͕ߴ͘ͳΔ • Ϣʔβʔ͔Βͷ؍఺Ͱςετ͠΍͍͢ https://dagger.dev/hilt/testing-philosophy.html ΑΓ
  103. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 31 ※ʮFIFA ϫʔϧυΧοϓ

    Χλʔϧ 2022ʯΛશ64ࢼ߹ແྉੜதܧ͢ΔʮABEMAʯ͕αοΧʔͷ ৽͍͠ࢹௌମݧΛଅਐ͢Δ৽ػೳΛൃදɺແྉͷʮݟಀ͠ϑϧϚον഑৴ʯ΍ʮϋΠϥΠτө૾ʯ ͳͲɺ11݄20೔ʢ೔ʣ։ນͱಉ࣌ʹఏڙ։࢝ https://www.cyberagent.co.jp/news/detail/id=28189
  104. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 31 • ࠓճͷ࣮૷Ͱ͸ը໘ͷঢ়ଶ͚ͩͰ΋ɺ์ૹͷ଴ػɺ์ૹͷ

    ࢹௌɺ௥͔͚ͬ࠶ੜͷࢹௌɺݟಀ͠഑৴ͷࢹௌ˞͕͋Γɺ ์ૹͷऴྃɺ։࢝ΛखಈͰςετ͢Δʹ͸ࣗ෼Ͱ൪૊Λฤ ੒ͯ͠ɺ์ૹ࣌ؒ·Ͱ଴ͬͨΓɺ์ૹऴྃΛ଴ͬͨΓ͢Δ ඞཁ͕͋Δɻ ※ʮFIFA ϫʔϧυΧοϓ Χλʔϧ 2022ʯΛશ64ࢼ߹ແྉੜதܧ͢ΔʮABEMAʯ͕αοΧʔͷ ৽͍͠ࢹௌମݧΛଅਐ͢Δ৽ػೳΛൃදɺແྉͷʮݟಀ͠ϑϧϚον഑৴ʯ΍ʮϋΠϥΠτө૾ʯ ͳͲɺ11݄20೔ʢ೔ʣ։ນͱಉ࣌ʹఏڙ։࢝ https://www.cyberagent.co.jp/news/detail/id=28189
  105. AbemaTV, Inc. All Rights Reserved ΋ͱ΋ͱͷ࣮૷ͷ໰୊఺ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 31 • ࠓճͷ࣮૷Ͱ͸ը໘ͷঢ়ଶ͚ͩͰ΋ɺ์ૹͷ଴ػɺ์ૹͷ

    ࢹௌɺ௥͔͚ͬ࠶ੜͷࢹௌɺݟಀ͠഑৴ͷࢹௌ˞͕͋Γɺ ์ૹͷऴྃɺ։࢝ΛखಈͰςετ͢Δʹ͸ࣗ෼Ͱ൪૊Λฤ ੒ͯ͠ɺ์ૹ࣌ؒ·Ͱ଴ͬͨΓɺ์ૹऴྃΛ଴ͬͨΓ͢Δ ඞཁ͕͋Δɻ • ViewModelɺUseCaseͳͲݸผʹࡉ͔͘ςετΛॻ͍͍ͯ ͯɺ࣮ࡍͷ໰୊ΛΩϟον͠ʹ͘͘ɺࡉ͔͍ϓϩύςΟ͕ ૿͑ΔͳͲͰςετ͕յΕͨΓվमΛೖΕΔࣄ͕ଟ͔ͬ ͨɻ ※ʮFIFA ϫʔϧυΧοϓ Χλʔϧ 2022ʯΛશ64ࢼ߹ແྉੜதܧ͢ΔʮABEMAʯ͕αοΧʔͷ ৽͍͠ࢹௌମݧΛଅਐ͢Δ৽ػೳΛൃදɺແྉͷʮݟಀ͠ϑϧϚον഑৴ʯ΍ʮϋΠϥΠτө૾ʯ ͳͲɺ11݄20೔ʢ೔ʣ։ນͱಉ࣌ʹఏڙ։࢝ https://www.cyberagent.co.jp/news/detail/id=28189
  106. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 32 Robolectric +

    Dagger HiltͰςετΛ࣮૷͢Δ • Robolectric ◦ ͭ·Γ࣮ߦ؀ڥ͸࣮ػͰ͸ͳ͘JVM্Ͱಈ͔͢ςε τɻEspressoͱಉ͡ςετίʔυͰςετΛॻ͚Δɻ
  107. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 32 Robolectric +

    Dagger HiltͰςετΛ࣮૷͢Δ • Robolectric ◦ ͭ·Γ࣮ߦ؀ڥ͸࣮ػͰ͸ͳ͘JVM্Ͱಈ͔͢ςε τɻEspressoͱಉ͡ςετίʔυͰςετΛॻ͚Δɻ ◦ UIʹؔͯ͠ຊ෺ͷΦϒδΣΫτͰ͸ͳ͘ͳΔ͕ɺςε τͷ҆ఆੑΛ֬อ͢ΔͨΊ
  108. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 32 Robolectric +

    Dagger HiltͰςετΛ࣮૷͢Δ • Robolectric ◦ ͭ·Γ࣮ߦ؀ڥ͸࣮ػͰ͸ͳ͘JVM্Ͱಈ͔͢ςε τɻEspressoͱಉ͡ςετίʔυͰςετΛॻ͚Δɻ ◦ UIʹؔͯ͠ຊ෺ͷΦϒδΣΫτͰ͸ͳ͘ͳΔ͕ɺςε τͷ҆ఆੑΛ֬อ͢ΔͨΊ • Hilt
  109. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 32 Robolectric +

    Dagger HiltͰςετΛ࣮૷͢Δ • Robolectric ◦ ͭ·Γ࣮ߦ؀ڥ͸࣮ػͰ͸ͳ͘JVM্Ͱಈ͔͢ςε τɻEspressoͱಉ͡ςετίʔυͰςετΛॻ͚Δɻ ◦ UIʹؔͯ͠ຊ෺ͷΦϒδΣΫτͰ͸ͳ͘ͳΔ͕ɺςε τͷ҆ఆੑΛ֬อ͢ΔͨΊ • Hilt ◦ ΞϓϦ಺ͰAPI͚ͩFakeʹஔ͖׵͑Λߦ͏
  110. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 32 Robolectric +

    Dagger HiltͰςετΛ࣮૷͢Δ • Robolectric ◦ ͭ·Γ࣮ߦ؀ڥ͸࣮ػͰ͸ͳ͘JVM্Ͱಈ͔͢ςε τɻEspressoͱಉ͡ςετίʔυͰςετΛॻ͚Δɻ ◦ UIʹؔͯ͠ຊ෺ͷΦϒδΣΫτͰ͸ͳ͘ͳΔ͕ɺςε τͷ҆ఆੑΛ֬อ͢ΔͨΊ • Hilt ◦ ΞϓϦ಺ͰAPI͚ͩFakeʹஔ͖׵͑Λߦ͏ ▪ Dagger HiltͷTestInstallIn()Λ࢖ͬͯAPI͚ͩஔ ͖׵͑Δɻ
  111. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 32 Robolectric +

    Dagger HiltͰςετΛ࣮૷͢Δ • Robolectric ◦ ͭ·Γ࣮ߦ؀ڥ͸࣮ػͰ͸ͳ͘JVM্Ͱಈ͔͢ςε τɻEspressoͱಉ͡ςετίʔυͰςετΛॻ͚Δɻ ◦ UIʹؔͯ͠ຊ෺ͷΦϒδΣΫτͰ͸ͳ͘ͳΔ͕ɺςε τͷ҆ఆੑΛ֬อ͢ΔͨΊ • Hilt ◦ ΞϓϦ಺ͰAPI͚ͩFakeʹஔ͖׵͑Λߦ͏ ▪ Dagger HiltͷTestInstallIn()Λ࢖ͬͯAPI͚ͩஔ ͖׵͑Δɻ ▪ ͜ΕʹΑΓ΄ͱΜͲͷ࣮૷͕ຊ෺ͷΦϒδΣΫ τͱͳΔ
  112. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 33 ௚઀View΍APIͷϨεϙϯεͳͲʹґଘ͢Δςε τΛॻ͘ͱมߋʹऑ͘ͳΔͷͰ͸ʁ

    
 ྫ͑͹λΠτϧͷViewͷIDॻ͖׵͑ͨΒશ෦ͷς ετॻ͖௚͢ͷʁ • → Robot PatternΛ࢖͏ɻ͜Ε͸ςετ ͷ”what”ͱ”how”Λ෼཭͢Δ˞
  113. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 33 ௚઀View΍APIͷϨεϙϯεͳͲʹґଘ͢Δςε τΛॻ͘ͱมߋʹऑ͘ͳΔͷͰ͸ʁ

    
 ྫ͑͹λΠτϧͷViewͷIDॻ͖׵͑ͨΒશ෦ͷς ετॻ͖௚͢ͷʁ • → Robot PatternΛ࢖͏ɻ͜Ε͸ςετ ͷ”what”ͱ”how”Λ෼཭͢Δ˞ ※ https://jakewharton.com/testing-robots/ΑΓ
  114. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 34 @Test fun

    shouldPlayButtonVisibleWhenFinishingProgram() { robot { setupServer(NotProgramStartedPattern) launchScreen() // ൪૊ऴྃ·Ͱ଴ͭ(TestCoroutineSchedulerΛ͍͡Δ) waitForFinishingProgram() checkPlayButtonVisible() } }
  115. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 34 @Test fun

    shouldPlayButtonVisibleWhenFinishingProgram() { robot { setupServer(NotProgramStartedPattern) launchScreen() // ൪૊ऴྃ·Ͱ଴ͭ(TestCoroutineSchedulerΛ͍͡Δ) waitForFinishingProgram() checkPlayButtonVisible() } } robot{}Ͱғͬͯɺςετͷ”how”͸ robotʹ΍ͬͯ΋Β͏
  116. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 34 @Test fun

    shouldPlayButtonVisibleWhenFinishingProgram() { robot { setupServer(NotProgramStartedPattern) launchScreen() // ൪૊ऴྃ·Ͱ଴ͭ(TestCoroutineSchedulerΛ͍͡Δ) waitForFinishingProgram() checkPlayButtonVisible() } } APIηοτΞοϓ͸͋Δ͕ɺ௚઀Ϩε ϙϯεΛࢦఆͨ͠Γ͠ͳ͍ (Robotʹ΍ͬͯ΋Β͏) robot{}Ͱғͬͯɺςετͷ”how”͸ robotʹ΍ͬͯ΋Β͏
  117. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 34 @Test fun

    shouldPlayButtonVisibleWhenFinishingProgram() { robot { setupServer(NotProgramStartedPattern) launchScreen() // ൪૊ऴྃ·Ͱ଴ͭ(TestCoroutineSchedulerΛ͍͡Δ) waitForFinishingProgram() checkPlayButtonVisible() } } APIηοτΞοϓ͸͋Δ͕ɺ௚઀Ϩε ϙϯεΛࢦఆͨ͠Γ͠ͳ͍ (Robotʹ΍ͬͯ΋Β͏) ViewͷνΣοΫ͸͋Δ͕௚઀IDΛݟͨ Γ͠ͳ͍ (Robotʹ΍ͬͯ΋Β͏) robot{}Ͱғͬͯɺςετͷ”how”͸ robotʹ΍ͬͯ΋Β͏
  118. AbemaTV, Inc. All Rights Reserved Robotͷ࣮૷͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 35 @Inject lateinit

    val robot: Robot @Test fun test() { robot { hoge() } } class Robot { operator fun invoke(block: Robot.() -> Unit) { runTest{ block() } } fun hoge() {...} }
  119. AbemaTV, Inc. All Rights Reserved Ͳ͏͔ͨ͠ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 36 @Test fun

    shouldPlayButtonVisibleWhenFinishingProgram() { robot { setupServer(NotProgramStartedPattern) launchScreen() // ൪૊ऴྃ·Ͱ଴ͭ waitForFinishingProgram() checkPlayButtonVisible() } } ςετͷ 
 ”What = ԿΛ͢Δ͔”ͱ”How = Ͳ͏΍ͬͯ΍Δ͔” Λ෼཭͢Δͷ͕Robot Patternɻ ςετ΋ΞϓϦͱಉ͡Α͏ʹઃܭ͢Δɻ
  120. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ
  121. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏
  122. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ
  123. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ ◦ Robolectricͩͱɺը໘ݟΕͳ͍ͷͰɺ΋͠ςετಈ͍ͯ΋ɺݟ͑ͳ͍ͷͰಈ͍͍ͯΔͷ͔Α͘෼͔Βͳ͍ɻෆ҆ɻ
  124. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ ◦ Robolectricͩͱɺը໘ݟΕͳ͍ͷͰɺ΋͠ςετಈ͍ͯ΋ɺݟ͑ͳ͍ͷͰಈ͍͍ͯΔͷ͔Α͘෼͔Βͳ͍ɻෆ҆ɻ ▪ → Roborazziͱ͍͏ՄࢹԽϥΠϒϥϦΛ࠷ۙ࡞ͬͨͷͰ࢖ͬͯݟ͍ͯͩ͘͞
  125. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ ◦ Robolectricͩͱɺը໘ݟΕͳ͍ͷͰɺ΋͠ςετಈ͍ͯ΋ɺݟ͑ͳ͍ͷͰಈ͍͍ͯΔͷ͔Α͘෼͔Βͳ͍ɻෆ҆ɻ ▪ → Roborazziͱ͍͏ՄࢹԽϥΠϒϥϦΛ࠷ۙ࡞ͬͨͷͰ࢖ͬͯݟ͍ͯͩ͘͞ ◦ Robolectric͸ActivityؒͷҠಈ͸ςετͰ͖ͳ͍ɻ
  126. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ ◦ Robolectricͩͱɺը໘ݟΕͳ͍ͷͰɺ΋͠ςετಈ͍ͯ΋ɺݟ͑ͳ͍ͷͰಈ͍͍ͯΔͷ͔Α͘෼͔Βͳ͍ɻෆ҆ɻ ▪ → Roborazziͱ͍͏ՄࢹԽϥΠϒϥϦΛ࠷ۙ࡞ͬͨͷͰ࢖ͬͯݟ͍ͯͩ͘͞ ◦ Robolectric͸ActivityؒͷҠಈ͸ςετͰ͖ͳ͍ɻ ▪ FragmentͳΒOK
  127. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ ◦ Robolectricͩͱɺը໘ݟΕͳ͍ͷͰɺ΋͠ςετಈ͍ͯ΋ɺݟ͑ͳ͍ͷͰಈ͍͍ͯΔͷ͔Α͘෼͔Βͳ͍ɻෆ҆ɻ ▪ → Roborazziͱ͍͏ՄࢹԽϥΠϒϥϦΛ࠷ۙ࡞ͬͨͷͰ࢖ͬͯݟ͍ͯͩ͘͞ ◦ Robolectric͸ActivityؒͷҠಈ͸ςετͰ͖ͳ͍ɻ ▪ FragmentͳΒOK • ୅ସखஈ
  128. AbemaTV, Inc. All Rights Reserved τϨʔυΦϑ͸ʁ୅ସखஈ͸ʁ “ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏”ΛͲ͏࢖͔ͬͨ 37 • τϨʔυΦϑ

    ◦ ςετͰ໰୊͕͋Δͱ͖ʹݪҼ͕෼͔Δ·Ͱʹ͕͔͔࣌ؒΔɻ ▪ → ׂͱࡉ͔Ίʹϝιουͷݺͼग़͠ͱ͔ͷϩάΛೖΕΔɻ • → ςετͰ͸ϩάϥΠϒϥϦ͕println()ʹϩάΛग़͢Α͏ʹTest RuleΛ࡞ͬͯͦΕΛ࢖͏ • → ύϑΥʔϚϯε؍఺ͰTimberCompat.d{} ͷΑ͏ͳܗͷؔ਺Λ༻ҙͯ͠ɺຊ൪Ͱ͸ϥϜμ಺Λ࣮ߦ͠ͳ͍͜ͱͰɺ toString()Λຊ൪Ͱ࣮ߦ͠ͳ͍Α͏ʹ͢Δɻ ◦ Robolectricͩͱɺը໘ݟΕͳ͍ͷͰɺ΋͠ςετಈ͍ͯ΋ɺݟ͑ͳ͍ͷͰಈ͍͍ͯΔͷ͔Α͘෼͔Βͳ͍ɻෆ҆ɻ ▪ → Roborazziͱ͍͏ՄࢹԽϥΠϒϥϦΛ࠷ۙ࡞ͬͨͷͰ࢖ͬͯݟ͍ͯͩ͘͞ ◦ Robolectric͸ActivityؒͷҠಈ͸ςετͰ͖ͳ͍ɻ ▪ FragmentͳΒOK • ୅ସखஈ ◦ ࠓճ͸ΞϓϦ಺ΛͳΔ΂͘౷߹ͨ͠ςετΛߦ͍ͬͯͨɻཧ૝తʹ͸E2E΋΍͍ͬͯ͘΂͖Ͱ͸͋Δ͕Ͳ͏͍͔ͯ͘͠ɻ
  129. AbemaTV, Inc. All Rights Reserved ·ͱΊ 38 • ҎԼͷݪଇΛར༻ͯ͠վળ͠ɺৼΓฦΓΛ͍͖ͯ͠·ͨ͠ɻ ◦

    Single Source of Truth ◦ Unidirectional Data Flow ◦ ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍
  130. AbemaTV, Inc. All Rights Reserved ·ͱΊ 38 • ҎԼͷݪଇΛར༻ͯ͠վળ͠ɺৼΓฦΓΛ͍͖ͯ͠·ͨ͠ɻ ◦

    Single Source of Truth ◦ Unidirectional Data Flow ◦ ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍ ◦ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏
  131. AbemaTV, Inc. All Rights Reserved ·ͱΊ 38 • ҎԼͷݪଇΛར༻ͯ͠վળ͠ɺৼΓฦΓΛ͍͖ͯ͠·ͨ͠ɻ ◦

    Single Source of Truth ◦ Unidirectional Data Flow ◦ ViewModel͔ΒUIʹΠϕϯτΛૹΒͳ͍ ◦ ςετͰ͸Ͱ͖Δ͚ͩຊ෺ͷΦϒδΣΫτΛ࢖͏ • ͜ͷΑ͏ͳ͞·͟·ͳऔΓ૊Έʹ͸ɺਖ਼ղ͸ͳ͍औΓ૊Έͩͱࢥ͍ͬͯΔΜͰ͕͢ɺΞ΢τ ϓοτͯ͠ɺؒҧ͍ͬͯͨΓɺࢹ఺ͱͯ͠ൈ͚͍ͯΔͱ͜ΖͳͲ͕͋Ε͹ɺϑΟʔυόοΫͳ Ͳ͔Βؾ͖ͮΛಘͨΓ͠ͳ͕Βվળ͍͚ͯ͠Δͱ͍͍ͳͱࢥ͍ͬͯ·͢ɻ