Slide 1

Slide 1 text

MVIʹجͮ͘StateMachineΞʔΩςΫνϟ Kotlin Multiplatform & Jetpack Compose & SwiftUI Λ૊Έ߹ΘͤΔ DroidKaigi 2023 マルコ&そば屋 1

Slide 2

Slide 2 text

2 Marco Valentino ߳ߓੜ·ΕɺதࠃҭͪɺΞϝϦΧͷେֶʹߦ͖ɺ೔ຊͰब৬͠·ͨ͠ɻ ڈ೥͔ΒגࣜձࣾΏΊΈͷAndroidςοΫϦʔυͱͯ͠ಇ͍͍ͯ·͢ɻ 2020೥ͷ಄͔ΒKotlin MultiplatformΛϓϩδΣΫτʹಋೖ͠ɺͦΕҎདྷ ͜ͷςΫϊϩδʔʹऔΓ૊ΜͰ͍·͢ɻ wasabi-muffin wasabimuffin8

Slide 3

Slide 3 text

3 sobaya-0141 sobaya15 ͦ͹԰ גࣜձࣾΏΊΈͰAndroidςοΫϦʔυʢ͓স͍୲౰ʣΛ͠ͳ͕Βٕज़ ޿ใΛ͍ͯ͠·͢ɻ ຖ೔ೋ೔ਲ͍ͳͷͰɺۤͦ͠͏ʹ͍ͯͨ͠Β༏͍ͯͩ͘͘͠͠͞ ※ SNSͰ΋ͳΜͰ΋͓ؾܰʹབྷΜͰ͍ͩ͘͞ʂʂ

Slide 4

Slide 4 text

ςΫϊϩδʔͷ঺հ 1 ▶︎ K o t l i n M u l t i p l a t f o r m の紹介 ▶︎ J e t p a c k C o m p o s e の紹介 ▶︎ S w i f t U I の紹介 MVI ΞʔΩςΫνϟ 2 ▶︎ M V I の紹介 ▶︎なぜ� M V I �を使用するのか ▶︎ M V I の実装方法 ( K M P ) ▶︎ M V I を使って画面を実装する ▶︎ M V I に基づく S t a t e M a c h i n e State Machine DSL 4 ▶︎ S t a t e M a c h i n e D S L �を使って リファクタリングする ▶︎開発上のメリット・デメリット Jetpack Compose & SwiftUIͷಋೖ 3 ▶︎ J e t p a c k C o m p o s e 上の使い方 ▶︎ S w i f t U I 上の使い方 4

Slide 5

Slide 5 text

ςΫϊϩδʔͷ঺հ

Slide 6

Slide 6 text

5 ςΫϊϩδʔͷ঺հ

Slide 7

Slide 7 text

5 ςΫϊϩδʔͷ঺հ

Slide 8

Slide 8 text

6 ςΫϊϩδʔͷ঺հ ▶︎ コードを削減 ▶︎ 直感的 ▶︎ 開発を加速させる ▶︎ パワフル ▶︎ 高度なアニメーション制御 ▶︎ シンプルになったデータフロー ▶︎ A P I の拡 ▶︎ 新タイプのグラフとインタラクティブな機能 Jetpack Compose SwiftUI

Slide 9

Slide 9 text

MVIΞʔΩςΫνϟͷ঺հ

Slide 10

Slide 10 text

7 MVIͷ঺հ Model View Intent

Slide 11

Slide 11 text

MVIͷ঺հ Model View Intent 7

Slide 12

Slide 12 text

MVIͷ঺հ Model Intent View 7

Slide 13

Slide 13 text

MVIͷ঺հ Model Intent View 7

Slide 14

Slide 14 text

# MVIͷ঺հ Model View Intent dispatch update notify 7

Slide 15

Slide 15 text

# MVIͷ঺հ Model View Intent dispatch update notify 7

Slide 16

Slide 16 text

# MVIͷ঺հ Model View Intent dispatch update notify

Slide 17

Slide 17 text

# MVIͷ঺հ Kotlin Multiplatform dispatch notify intent state Android / iOS 8

Slide 18

Slide 18 text

# MVIͷ঺հ dispatch notify Processor Reducer Android / iOS intent state 8

Slide 19

Slide 19 text

# MVIͷ঺հ dispatch notify Processor Reducer emit Android / iOS intent state action 8

Slide 20

Slide 20 text

# MVIͷ঺հ dispatch notify Processor Reducer emit Android / iOS intent state action 8

Slide 21

Slide 21 text

# MVIͷ঺հ dispatch notify intent state Processor Reducer emit action Android / iOS send event 8

Slide 22

Slide 22 text

9 MVIͷ঺հ intent state Reducer action Processor Component Contract

Slide 23

Slide 23 text

10 MVIͷ঺հ Immutability 1 Unidirectional Data Flow 2

Slide 24

Slide 24 text

MVIΞʔΩςΫνϟͷ࣮૷ https://github.com/fika-tech/Macaron スライドに収めるために正しいコード スタイル使っていません !

Slide 25

Slide 25 text

sealed interface Contract interface Intent : Contract interface Action : Contract { interface Event : Action } interface State : Contract # macaron-core/../contract/Contract.kt macaron-core/../contract/Contract.kt 11

Slide 26

Slide 26 text

sealed interface Contract interface Intent : Contract interface Action : Contract { interface Event : Action } interface State : Contract 11 macaron-core/../contract/Contract.kt macaron-core/../contract/Contract.kt

Slide 27

Slide 27 text

12 macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job }

Slide 28

Slide 28 text

# macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } 12

Slide 29

Slide 29 text

# macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } 12

Slide 30

Slide 30 text

# macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } 12

Slide 31

Slide 31 text

# macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } 12

Slide 32

Slide 32 text

# macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } 12

Slide 33

Slide 33 text

# macaron-core/../components/Store.kt interface Store { val state: StateFlow val event: Flow fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } 12

Slide 34

Slide 34 text

Slide 35

Slide 35 text

13 macaron-core/../components/Processor.kt interface Processor { suspend fun process(intent: I, state: S): Flow } macaron-core/../components/Reducer.kt interface Reducer { suspend fun reduce(action: A, state: S): S }

Slide 37

Slide 37 text

15 macaron-core/../components/DefaultStore.kt override fun dispatch(intent:aI a ) a {a scope.launch { intents.emit(intent)a}a a }a private val intents = MutableSharedFlow(...)

Slide 38

Slide 38 text

# macaron-core/../components/DefaultStore.kt private val intents = MutableSharedFlow(...) init0{0 scope.launch0{0 intents0 0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 }4 } } 15

Slide 39

Slide 39 text

# macaron-core/../components/DefaultStore.kt intents0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 15

Slide 40

Slide 40 text

# macaron-core/../components/DefaultStore.kt intents0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 15

Slide 41

Slide 41 text

# macaron-core/../components/DefaultStore.kt intents0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 15

Slide 42

Slide 42 text

# macaron-core/../components/DefaultStore.kt intents0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 15

Slide 43

Slide 43 text

# macaron-core/../components/DefaultStore.kt intents0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 private val _state = MutableStateFlow(initialState) override val state: StateFlow = _state 15

Slide 44

Slide 44 text

# macaron-core/../components/DefaultStore.kt intents0 .flatMapMerge0{0intent0->0 processor.process(intent,0currentState)0 }0 .map0{0action0->0 if0(action0is0Action.Event)0send(action)0 reducer.reduce(action,0currentState) }2 .collect0{0state0->0_state.value0=0state }3 private val _state = MutableStateFlow(initialState) override val state: StateFlow = _state 15

Slide 45

Slide 45 text

MVIΛ࢖ͬͯը໘Λ࣮૷͢Δ https://github.com/fika-tech/DroidKaigiSample 発表資料とサンプルで使っている 画像は著作権がフリーなものです。 リンクは最後にまとめています。 !

Slide 46

Slide 46 text

16 ローディング リスト 初期エラー ページ ローディング ページエラー

Slide 47

Slide 47 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 48

Slide 48 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 49

Slide 49 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 50

Slide 50 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 51

Slide 51 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 52

Slide 52 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 53

Slide 53 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 54

Slide 54 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 55

Slide 55 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 詳細 16

Slide 56

Slide 56 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 57

Slide 57 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 58

Slide 58 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 59

Slide 59 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 60

Slide 60 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 61

Slide 61 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 62

Slide 62 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 63

Slide 63 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 64

Slide 64 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 65

Slide 65 text

# ローディング リスト 初期エラー ページ ローディング ページエラー 16

Slide 66

Slide 66 text

17 Domain Contract Processor Reducer

Slide 67

Slide 67 text

# Domain Contract Processor Reducer 17

Slide 68

Slide 68 text

# Domain Contract Processor Reducer 17

Slide 69

Slide 69 text

# Domain Contract Processor Reducer 17

Slide 70

Slide 70 text

18 Domain Contract Processor Reducer shared/../entities/Monster.kt data class Monster( val id: Int, val name: String, val imageUrl: String, ) shared/../data/repositories/MonsterRepository.kt interface MonsterRepository { suspend fun getMonster( offset: Int, limit: Int, ): List }

Slide 71

Slide 71 text

19 shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 0 }1 Domain Contract Processor Reducer

Slide 72

Slide 72 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 0 }1 Domain Contract Processor Reducer 19

Slide 73

Slide 73 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 0 }1 Domain Contract Processor Reducer ローディング 19

Slide 74

Slide 74 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 Domain Contract Processor Reducer 初期エラー 19

Slide 75

Slide 75 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 Domain Contract Processor Reducer 19

Slide 76

Slide 76 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 Domain Contract Processor Reducer リスト ページエラー ページ ローディング 19

Slide 77

Slide 77 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 sealed class0Stable :0MonsterListState() {1 abstract0val0monsterList:0List0 }0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 リスト ページエラー ページ ローディング Domain Contract Processor Reducer 19

Slide 78

Slide 78 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 sealed class0Stable :0MonsterListState() {1 abstract0val0monsterList:0List0 data0class0List( override0val0monsterList:0List )0:0Stable()0 }0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 リスト Domain Contract Processor Reducer 19

Slide 79

Slide 79 text

# shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 sealed class0Stable :0MonsterListState() {1 abstract0val0monsterList:0List0 data0class0List( override0val0monsterList:0List )0:0Stable()0 data0class0PageLoading( override0val0monsterList:0List )0:0Stable()0 }0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 ページ ローディング Domain Contract Processor Reducer 19

Slide 80

Slide 80 text

19 shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 sealed class0Stable :0MonsterListState() {1 abstract0val0monsterList:0List0 data0class0List( override0val0monsterList:0List )0:0Stable()0 data0class0PageLoading( override0val0monsterList:0List )0:0Stable()0 data0class0PageError( override0val0monsterList:0List,0 val error:0Throwable )0:0Stable() }0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 ページエラー Domain Contract Processor Reducer

Slide 81

Slide 81 text

shared/../feature/monsterList/MonsterListIntent.kt 20 sealed class MonsterListIntent : Intent { } Domain Contract Processor Reducer

Slide 82

Slide 82 text

shared/../feature/monsterList/MonsterListIntent.kt # sealed class MonsterListIntent : Intent { data object OnInit : MonsterListIntent() } Domain Contract Processor Reducer 20

Slide 83

Slide 83 text

shared/../feature/monsterList/MonsterListIntent.kt # sealed class MonsterListIntent : Intent { data object OnInit : MonsterListIntent() data class ClickItem( val monster: Monster ) : MonsterListIntent() } リスト Domain Contract Processor Reducer 20

Slide 84

Slide 84 text

shared/../feature/monsterList/MonsterListIntent.kt 20 sealed class MonsterListIntent : Intent { data object OnInit : MonsterListIntent() data class ClickItem( val monster: Monster ) : MonsterListIntent() data object ClickErrorRetry : MonsterListIntent() } エラー ページエラー Domain Contract Processor Reducer

Slide 85

Slide 85 text

shared/../feature/monsterList/MonsterListIntent.kt # sealed class MonsterListIntent : Intent { data object OnInit : MonsterListIntent() data class ClickItem( val monster: Monster ) : MonsterListIntent() data object ClickErrorRetry : MonsterListIntent() data object OnScrollToBottom : MonsterListIntent() } Domain Contract Processor Reducer 20

Slide 86

Slide 86 text

shared/../feature/monsterList/MonsterListAction.kt 21 sealed class MonsterListAction : Action { } Domain Contract Processor Reducer

Slide 87

Slide 87 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() } Domain Contract Processor Reducer ローディング 21

Slide 88

Slide 88 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() data class LoadSuccess( val monsterList: List ) : MonsterListAction() } Domain Contract Processor Reducer ローディング リスト 21

Slide 89

Slide 89 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() data class LoadSuccess( val monsterList: List ) : MonsterListAction() data class LoadError( val error: Throwable ) : MonsterListAction() } Domain Contract Processor Reducer エラー ローディング 21

Slide 90

Slide 90 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() data class LoadSuccess( val monsterList: List ) : MonsterListAction() data class LoadError( val error: Throwable ) : MonsterListAction() } Domain Contract Processor Reducer リスト ページ ローディング 21

Slide 91

Slide 91 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() data class LoadSuccess( val monsterList: List ) : MonsterListAction() data class LoadError( val error: Throwable ) : MonsterListAction() } Domain Contract Processor Reducer リスト ページ ローディング 21

Slide 92

Slide 92 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() data class LoadSuccess( val monsterList: List ) : MonsterListAction() data class LoadError( val error: Throwable ) : MonsterListAction() } Domain Contract Processor Reducer ページエラー ページ ローディング 21

Slide 93

Slide 93 text

shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object Loading : MonsterListAction() data class LoadSuccess( val monsterList: List ) : MonsterListAction() data class LoadError( val error: Throwable ) : MonsterListAction() data class NavigateDetails( val monster: Monster ) : MonsterListAction(), Action.Event } Domain Contract Processor Reducer 詳細 21

Slide 94

Slide 94 text

22 shared/../feature/monsterList/MonsterProcessor.kt class MonsterListProcessor( private val repository: MonsterRepository, ) : Processor { override suspend fun process( intent: MonsterListIntent, state: MonsterListState, ): Flow = flow { when (intent) { isaMonsterListIntent.OnInita-> TODO() isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a } } Domain Contract Processor Reducer

Slide 95

Slide 95 text

# shared/../feature/monsterList/MonsterProcessor.kt class MonsterListProcessor( private val repository: MonsterRepository, ) : Processor { override suspend fun process( intent: MonsterListIntent, state: MonsterListState, ): Flow = flow { when (intent) { isaMonsterListIntent.OnInita-> TODO() isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a } } Domain Contract Processor Reducer 22

Slide 96

Slide 96 text

# shared/../feature/monsterList/MonsterProcessor.kt class MonsterListProcessor( private val repository: MonsterRepository, ) : Processor { override suspend fun process( intent: MonsterListIntent, state: MonsterListState, ): Flow = flow { when (intent) { isaMonsterListIntent.OnInita-> TODO() isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a } } Domain Contract Processor Reducer 22

Slide 97

Slide 97 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> TODO() isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 98

Slide 98 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) } isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 99

Slide 99 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) repository.getMonster(offset = 0, limit = 20) } isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 100

Slide 100 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatching { repository.getMonster(offset = 0, limit = 20) }.onSuccess { monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } } isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 101

Slide 101 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatching { repository.getMonster(offset = 0, limit = 20) }.onSuccess { monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) }.onFailure { errora-> emit(MonsterListAction.LoadError(error)) } } isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 102

Slide 102 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 103

Slide 103 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { emit(MonsterListAction.NavigateDetails(intent.monster)) } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 104

Slide 104 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 105

Slide 105 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) }.onFailurea{ errora-> emit(MonsterListAction.LoadError(error)) } } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 106

Slide 106 text

# shared/../feature/monsterList/MonsterProcessor.kt private suspend fun loadMonsterList(a offset: Int, repository: Repository, emit: suspenda(MonsterListAction a ) a -> Unit, a )a{a emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = offset, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) }.onFailurea{ errora-> emit(MonsterListAction.LoadError(error)) } }a Domain Contract Processor Reducer 22

Slide 107

Slide 107 text

# shared/../feature/monsterList/MonsterProcessor.kt private suspend fun loadMonsterList(a offset: Int, repository: Repository, emit: suspenda(MonsterListAction a ) a -> Unit, a )a{a emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = offset, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) }.onFailurea{ errora-> emit(MonsterListAction.LoadError(error)) } }a Domain Contract Processor Reducer 22

Slide 108

Slide 108 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) }.onFailurea{ errora-> emit(MonsterListAction.LoadError(error)) } } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 109

Slide 109 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { loadMonsterList(offset = 0, repository, ::emit) } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 110

Slide 110 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 111

Slide 111 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { loadMonsterList(offset = ???, repository, ::emit) } isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 112

Slide 112 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { loadMonsterList(offset = ???, repository, ::emit) } isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 113

Slide 113 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { loadMonsterList(offset = ???, repository, ::emit) } isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer offset = 0 offset = monsterList.size 22

Slide 114

Slide 114 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> when (state) { is MonsterListState.Errora-> { loadMonsterList(offset = 0, repository, ::emit) } is MonsterListState.Stable.PageErrora-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } is MonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 115

Slide 115 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> when (state) { is MonsterListState.Errora-> { loadMonsterList(offset = 0, repository, ::emit) } is MonsterListState.Stable.PageErrora-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } is MonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 116

Slide 116 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> when (state) { is MonsterListState.Errora-> { loadMonsterList(offset = 0, repository, ::emit) } is MonsterListState.Stable.PageErrora-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } is MonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 117

Slide 117 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { ... } isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22

Slide 118

Slide 118 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { ... } isaMonsterListIntent.OnScrollToBottoma-> when (state) { isaMonsterListState.Stable.Lista-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } }a Domain Contract Processor Reducer 22

Slide 119

Slide 119 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { ... } isaMonsterListIntent.OnScrollToBottoma-> when (state) { isaMonsterListState.Stable.Lista-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } }a Domain Contract Processor Reducer 22

Slide 120

Slide 120 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { ... } isaMonsterListIntent.OnScrollToBottoma-> when (state) { isaMonsterListState.Stable.Lista-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } }a Domain Contract Processor Reducer 22

Slide 121

Slide 121 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema-> { ... } isaMonsterListIntent.ClickErrorRetrya-> { ... } isaMonsterListIntent.OnScrollToBottoma-> when (state) { isaMonsterListState.Stable.Lista-> { loadMonsterList( offset = state.monsterList.size, repository, ::emit ) } else -> Unit } }a Domain Contract Processor Reducer 22

Slide 122

Slide 122 text

# shared/../feature/monsterList/MonsterProcessor.kt when (intent) { is MonsterListIntent.OnInit -> when (state) { is MonsterListState.Initial -> loadMonsterList(0, repository, ::emit) } is MonsterListIntent.ClickItem -> when (state) { is MonsterListState.Stable -> { emit(MonsterListAction.NavigateDetails(intent.monster)) } } is MonsterListIntent.ClickErrorRetry -> when (state) { is MonsterListState.Error -> loadMonsterList(0, repository, ::emit) is MonsterListState.Stable.PageError -> loadMonsterList(state.currentOffset, repository, ::emit ) } is MonsterListIntent.OnScrollToBottom -> when (state) { is MonsterListState.Stable.List -> loadMonsterList(state.currentOffset, repository, ::emit) } } Domain Contract Processor Reducer 22

Slide 123

Slide 123 text

23 shared/../feature/monsterList/MonsterReducer.kt class MonsterListReducer : Reducer { override suspend fun reduce( action: MonsterListAction, state: MonsterListState, ): MonsterListState { return when (action) { is MonsterListAction.Loading -> TODO() is MonsterListAction.LoadSuccess -> TODO() is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } } } Domain Contract Processor Reducer

Slide 124

Slide 124 text

# shared/../feature/monsterList/MonsterReducer.kt class MonsterListReducer : Reducer { override suspend fun reduce( action: MonsterListAction, state: MonsterListState, ): MonsterListState { return when (action) { is MonsterListAction.Loading -> TODO() is MonsterListAction.LoadSuccess -> TODO() is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } } } Domain Contract Processor Reducer 23

Slide 125

Slide 125 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> TODO() is MonsterListAction.LoadSuccess -> TODO() is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 126

Slide 126 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) is MonsterListState.Stable.Error -> MonsterListState.Stable.PageLoading(state.monsterList) else -> state } .. } Domain Contract Processor Reducer 23

Slide 127

Slide 127 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) is MonsterListState.Stable.Error -> MonsterListState.Stable.PageLoading(state.monsterList) else -> state } .. } Domain Contract Processor Reducer 23

Slide 128

Slide 128 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) is MonsterListState.Stable.Error -> MonsterListState.Stable.PageLoading(state.monsterList) else -> state } .. } Domain Contract Processor Reducer 23

Slide 129

Slide 129 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) is MonsterListState.Stable.Error -> MonsterListState.Stable.PageLoading(state.monsterList) else -> state } .. } Domain Contract Processor Reducer 23

Slide 130

Slide 130 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) is MonsterListState.Stable.Error -> MonsterListState.Stable.PageLoading(state.monsterList) else -> state } .. } Domain Contract Processor Reducer 23

Slide 131

Slide 131 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) is MonsterListState.Stable.Error -> MonsterListState.Stable.PageLoading(state.monsterList) else -> state } .. } Domain Contract Processor Reducer 23

Slide 132

Slide 132 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> TODO() is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 133

Slide 133 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List( state.monsterList + action.monsterList ) else -> state } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 134

Slide 134 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List( state.monsterList + action.monsterList ) else -> state } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 135

Slide 135 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List( state.monsterList + action.monsterList ) else -> state } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 136

Slide 136 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List( state.monsterList + action.monsterList ) else -> state } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 137

Slide 137 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List( state.monsterList + action.monsterList ) else -> state } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 138

Slide 138 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List( state.monsterList + action.monsterList ) else -> state } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 139

Slide 139 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> TODO() is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 140

Slide 140 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> when (state) { is MonsterListState.Loading -> MonsterListState.Error(action.error) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.PageError( state.monsterList, action.error ) } else -> state } is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 141

Slide 141 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> when (state) { is MonsterListState.Loading -> MonsterListState.Error(action.error) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.PageError( state.monsterList, action.error ) } else -> state } is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 142

Slide 142 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> when (state) { is MonsterListState.Loading -> MonsterListState.Error(action.error) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.PageError( state.monsterList, action.error ) } else -> state } is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 143

Slide 143 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> when (state) { is MonsterListState.Loading -> MonsterListState.Error(action.error) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.PageError( state.monsterList, action.error ) } else -> state } is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 144

Slide 144 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> when (state) { is MonsterListState.Loading -> MonsterListState.Error(action.error) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.PageError( state.monsterList, action.error ) } else -> state } is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 145

Slide 145 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> { ... } is MonsterListAction.NavigateDetails -> TODO() } Domain Contract Processor Reducer 23

Slide 146

Slide 146 text

# shared/../feature/monsterList/MonsterReducer.kt when (action) { is MonsterListAction.Loading -> { ... } is MonsterListAction.LoadSuccess -> { ... } is MonsterListAction.LoadError -> { ... } is MonsterListAction.NavigateDetails -> state } Domain Contract Processor Reducer 23

Slide 147

Slide 147 text

# shared/../feature/monsterList/MonsterReducer.kt Domain Contract Processor Reducer when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading is MonsterListState.Stable.List -> MonsterListState.Stable.PageLoading(state.monsterList) } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.List(state.monsterList + action.monsterList) } is MonsterListAction.LoadError -> when (state) { is MonsterListState.Loading -> MonsterListState.Error(action.error) is MonsterListState.Stable.PageLoading -> MonsterListState.Stable.PageError(state.monsterList, action.error) } is MonsterListAction.NavigateDetails -> state } 23

Slide 148

Slide 148 text

24 Reducer Processor Store

Slide 149

Slide 149 text

25 dispatch notify intent state Processor Reducer emit action Android / iOS send event Store

Slide 150

Slide 150 text

26 when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer

Slide 151

Slide 151 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 152

Slide 152 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 153

Slide 153 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 154

Slide 154 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 155

Slide 155 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 156

Slide 156 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 157

Slide 157 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 158

Slide 158 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 159

Slide 159 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 160

Slide 160 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 161

Slide 161 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 162

Slide 162 text

# when (intent) { isaMonsterListIntent.OnInita-> { emit(MonsterListAction.Loading) runCatchinga{ repository.getMonster(offset = 0, limit = 20) }.onSuccessa{ monsterLista-> emit(MonsterListAction.LoadSuccess(monsterList)) } ... } ... } when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } is MonsterListAction.LoadSuccess -> when (state) { is MonsterListState.Loading -> MonsterListState.Stable.List(action.monsterList) ... } ... } // Processor // Reducer 26

Slide 163

Slide 163 text

27 Reducer Processor Store

Slide 164

Slide 164 text

͢ͰʹεςʔτϚγϯ Λ࡞͍ͬͯΔʂ 28

Slide 165

Slide 165 text

MVIʹج͘StateMachine

Slide 166

Slide 166 text

State Machineͱ͸ʁ 29 ݻମ ӷମ ؾମ

Slide 167

Slide 167 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ ӷମ ؾମ 29

Slide 168

Slide 168 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ ؾମ 29

Slide 169

Slide 169 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ ؾମ 29

Slide 170

Slide 170 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ ؾମ 29

Slide 171

Slide 171 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ 29

Slide 172

Slide 172 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ 29

Slide 173

Slide 173 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ on: ڽॖ(ӷԽ) 29

Slide 174

Slide 174 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ on: ڽॖ(ӷԽ) transition: ӷମ 29

Slide 175

Slide 175 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ on: ڽॖ(ӷԽ) transition: ӷମ 29

Slide 176

Slide 176 text

State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ on: ڽॖ transition: 🙅 ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ on: ڽॖ(ӷԽ) transition: ӷମ 29

Slide 177

Slide 177 text

30 MVIΛجͮ͘StateMachine // Processor when (intent) { is MonsterListIntent -> when (state) { is MonsterListState -> { ... } } } // Reducer when (action) { is MonsterAction -> when (state) { is MonsterListState -> { ... } } }

Slide 178

Slide 178 text

# MVIΛجͮ͘StateMachine // Processor when (intent) { is MonsterListIntent -> when (state) { is MonsterListState -> { ... } } } // Reducer when (action) { is MonsterAction -> when (state) { is MonsterListState -> { ... } } } 30

Slide 179

Slide 179 text

# MVIΛجͮ͘StateMachine // Processor when (intent) { is MonsterListIntent -> when (state) { is MonsterListState -> { ... } } } // Reducer when (action) { is MonsterAction -> when (state) { is MonsterListState -> { ... } } } 30

Slide 180

Slide 180 text

# MVIΛجͮ͘StateMachine // Processor when (intent) { is MonsterListIntent -> when (state) { is MonsterListState -> { ... } } } // Processor when (state) { is MonsterListState -> when (intent) { is MonsterListIntent -> { ... } } } // Reducer when (action) { is MonsterAction -> when (state) { is MonsterListState -> { ... } } } // Reducer when (state) { is MonsterListState -> when (action) { is MonsterListAction -> { ... } } } 30

Slide 181

Slide 181 text

# MVIΛجͮ͘StateMachine // Processor when (intent) { is MonsterListIntent -> when (state) { is MonsterListState -> { ... } } } // Processor when (state) { is MonsterListState -> when (intent) { is MonsterListIntent -> { ... } } } // Reducer when (action) { is MonsterAction -> when (state) { is MonsterListState -> { ... } } } // Reducer when (state) { is MonsterListState -> when (action) { is MonsterListAction -> { ... } } } 30

Slide 182

Slide 182 text

31 shared/../feature/monsterList/MonsterProcessor.kt // Processor when (intent) { is MonsterListIntent.OnInit -> when (state) { is MonsterListState.Initial -> loadMonsterList(0, repository, ::emit) } ... } // Reducer when (action) { is MonsterListAction.Loading -> when (state) { is MonsterListState.Initial -> MonsterListState.Loading ... } ... }

Slide 183

Slide 183 text

# shared/../feature/monsterList/MonsterProcessor.kt // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> loadMonsterList(0, repository, ::emit) } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... } 31

Slide 184

Slide 184 text

32 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess) // or emit(MonsterListAction.LoadError) } } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... }

Slide 185

Slide 185 text

4 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess) // or emit(MonsterListAction.LoadError) } } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... } 32

Slide 186

Slide 186 text

4 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess) // or emit(MonsterListAction.LoadError) } } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... } 32

Slide 187

Slide 187 text

4 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess) // or emit(MonsterListAction.LoadError) } } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... } 32

Slide 188

Slide 188 text

4 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess) // or emit(MonsterListAction.LoadError) } } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... } 32

Slide 189

Slide 189 text

4 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError // Processor when (state) { is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess) // or emit(MonsterListAction.LoadError) } } ... } // Reducer when (state) { is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> MonsterListState.Loading ... } ... } 32

Slide 190

Slide 190 text

33 Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError Stable intent: ClickItem event: NavigateDetails Error intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error Error intent: ClickErrorRetry action: Loading reduce: State.Stable.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error

Slide 191

Slide 191 text

# Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError Stable intent: ClickItem event: NavigateDetails Error intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error Error intent: ClickErrorRetry action: Loading reduce: State.Stable.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error 33

Slide 192

Slide 192 text

# Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError Stable intent: ClickItem event: NavigateDetails Error intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error Error intent: ClickErrorRetry action: Loading reduce: State.Stable.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error ݻମ on: ༥ղ transition: ӷମ ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ on: ڽॖ(ӷԽ) transition: ӷମ 33

Slide 193

Slide 193 text

# Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError Stable intent: ClickItem event: NavigateDetails Error intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error Error intent: ClickErrorRetry action: Loading reduce: State.Stable.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error 33

Slide 194

Slide 194 text

Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess|LoadError intent: ClickErrorRetry action: 🙅 # Stable intent: ClickItem event: NavigateDetails Error intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Initial intent: OnInit action: Loading reduce: State.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error Error intent: ClickErrorRetry action: Loading reduce: State.Stable.Loading action: LoadSuccess | LoadError Loading receive: LoadSuccess reduce: State.Stable.List receive: LoadError reduce: State.Stable.Error 33

Slide 195

Slide 195 text

34 MonsterListState Stable I.ClickMonsterEntry > E.NavigateDetails Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading Error I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] Stable.Initial I.OnScrollToBottom > A.Loading, [A.LoadSuccess | A.LoadError] Stable.PageLoading Stable.PageError I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] A.Loading A.LoadSuccess A.LoadError A.Loading A.Loading A.LoadSuccess A.LoadError A.Loading PlantUML Diagram

Slide 196

Slide 196 text

Jetpack Compose & SwiftUIͷಋೖ

Slide 197

Slide 197 text

34 Figma ࢓༷ॻ UMLਤ

Slide 198

Slide 198 text

35 androidApp/../ui/screen/MonsterListScreen.kt @Composable fun MonsterListScreen( contract: Contract<...>, ) { LaunchedEffect(Unit) { contract.dispatch(MonsterListIntent.OnInit) } contract.handleEvents { action -> when (action) { is MonsterListAction.NavigateDetails -> Timber.d("Handle Navigation") else -> Unit } } MonsterListContent(contract.state, contract.dispatch) }

Slide 199

Slide 199 text

# androidApp/../ui/screen/MonsterListScreen.kt @Composable fun MonsterListScreen( contract: Contract<...>, ) { LaunchedEffect(Unit) { contract.dispatch(MonsterListIntent.OnInit) } contract.handleEvents { action -> when (action) { is MonsterListAction.NavigateDetails -> Timber.d("Handle Navigation") else -> Unit } } MonsterListContent(contract.state, contract.dispatch) } MonsterListState I.ClickMonster Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading Error I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] I.OnScrollTo A.Loading A.LoadSucc A.LoadError A.Loading 35

Slide 200

Slide 200 text

36 androidApp/../ui/screen/MonsterListScreen.kt state.rendera{ ... } state.rendera{ val lazyListState = rememberLazyListState().apply { onScrolledToBottom { dispatch(MonsterListIntent.OnScrollToBottom) } } LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize()) { items(items = monsterList) { monster -> MonsterListItem(name = monster.name, imageUrl = monster.imageUrl, onClick = { dispatch(MonsterListIntent.ClickItem(monster = monster)) }) } state.renderItemsa{ item { PageLoadingIndicatorItem() } } state.renderItemsa{ item { PageErrorItem(error = it.error, onClickRetry = { dispatch(MonsterListIntent.ClickErrorRetry) }) } } } } }

Slide 201

Slide 201 text

# androidApp/../ui/screen/MonsterListScreen.kt state.rendera{ LoadingIndicator() } state.rendera{ ... } state.rendera{ ... } Loading Error A.LoadError A.Loading 36

Slide 202

Slide 202 text

# androidApp/../ui/screen/MonsterListScreen.kt state.rendera{ ... } state.rendera{ val lazyListState = rememberLazyListState().apply { onScrolledToBottom { dispatch(MonsterListIntent.OnScrollToBottom) } } LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize()) { items(items = monsterList) { monster -> MonsterListItem(name = monster.name, imageUrl = monster.imageUrl, onClick = { dispatch(MonsterListIntent.ClickItem(monster = monster)) }) } state.renderItemsa{ item { PageLoadingIndicatorItem() } } state.renderItemsa{ item { PageErrorItem(error = it.error, onClickRetry = { dispatch(MonsterListIntent.ClickErrorRetry) }) } } } } } MonsterListState Stable I.ClickMonsterEntry > E.NavigateDetails Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading Error I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] Stable.Initial I.OnScrollToBottom > A.Loading, [A.LoadSuccess | A.LoadError] Stable.PageLoading Stable.PageError I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] A.Loading A.LoadSuccess A.LoadError A.Loading A.Loading A.LoadSuccess A.LoadError A.Loading 36

Slide 203

Slide 203 text

# androidApp/../ui/screen/MonsterListScreen.kt state.render { ... } state.render { val lazyListState = rememberLazyListState().apply { onScrolledToBottom { dispatch(MonsterListIntent.OnScrollToBottom) } } LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize()) { items(items = monsterList) { monster -> MonsterListItem(name = monster.name, imageUrl = monster.imageUrl, onClick = { dispatch(MonsterListIntent.ClickItem(monster = monster)) }) } state.renderItems { item { PageLoadingIndicatorItem() } } state.renderItems { item { PageErrorItem(error = it.error, onClickRetry = { dispatch(MonsterListIntent.ClickErrorRetry) }) } } } } } Stable.PageLoading Stable.PageError I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] A.Loading A.LoadSuccess A.LoadError A.Loading 36

Slide 204

Slide 204 text

# androidApp/../ui/screen/MonsterListScreen.kt state.render { ... } state.render { val lazyListState = rememberLazyListState().apply { onScrolledToBottom { dispatch(MonsterListIntent.OnScrollToBottom) } } LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize()) { items(items = monsterList) { monster -> MonsterListItem(name = monster.name, imageUrl = monster.imageUrl, onClick = { dispatch(MonsterListIntent.ClickItem(monster = monster)) }) } state.renderItems { item { PageLoadingIndicatorItem() } } state.renderItems { item { PageErrorItem(error = it.error, onClickRetry = { dispatch(MonsterListIntent.ClickErrorRetry) }) } } } } } Stable.PageLoading Stable.PageError I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] A.Loading A.LoadSuccess A.LoadError A.Loading 36

Slide 205

Slide 205 text

37 androidApp/../ui/screen/MonsterListScreen.kt @Composable fun MonsterListScreen( contract: Contract<...>, ) { LaunchedEffect(Unit) { contract.dispatch(MonsterListIntent.OnInit) } contract.handleEvents { action -> when (action) { is MonsterListAction.NavigateDetails -> Timber.d("Handle Navigation") else -> Unit } } MonsterListContent(contract.state, contract.dispatch) } Stable I.ClickMonsterEntry > E.NavigateDetails Loading Error I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadError] Stable.Initial I.OnScrollToBottom > A.Loading, [A.LoadSuccess | A.Load Stable.PageLoading Stable.PageError I.ClickErrorRetry > A.Loading, [A.LoadSuccess | A.LoadE A.Loading A.LoadSuccess A.LoadError A.Loading A.Loading A.LoadSuccess A.LoadError A.Loading

Slide 206

Slide 206 text

38 androidApp/../core/Contract.kt data class Contract( val state: S, val dispatch: (I) -> Unit = {}, val event: A? = null, val process: (A) -> Unit = {}, ) @Composable fun contract( store: Store, ): Contract { val state by store.state.collectAsState() val event by store.event.collectAsState(initial = null) return Contract(state, event, store::dispatch, store::process) }

Slide 210

Slide 210 text

40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract var body: some View { ZStack { switch contract.state { case _ as MonsterListState.Loading: LoadingIndicator() case let state as MonsterListState.Stable: MonsterListView(state: state, dispatch: contract.dispatch) case let state as MonsterListState.Error: FullScreenErrorView( error: state.error, onClickRetry: { contract.dispatch(MonsterListIntent.ClickErrorRetry()) } ) default: EmptyView() } }.onAppear { contract.dispatch(.OnInit()) } } }

Slide 211

Slide 211 text

40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract var body: some View { ZStack { switch contract.state { case _ as MonsterListState.Loading: LoadingIndicator() case let state as MonsterListState.Stable: MonsterListView(state: state, dispatch: contract.dispatch) case let state as MonsterListState.Error: FullScreenErrorView( error: state.error, onClickRetry: { contract.dispatch(MonsterListIntent.ClickErrorRetry()) } ) default: EmptyView() } }.onAppear { contract.dispatch(.OnInit()) } } }

Slide 212

Slide 212 text

40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract var body: some View { ZStack { switch contract.state { case _ as MonsterListState.Loading: LoadingIndicator() case let state as MonsterListState.Stable: MonsterListView(state: state, dispatch: contract.dispatch) case let state as MonsterListState.Error: FullScreenErrorView( error: state.error, onClickRetry: { contract.dispatch(MonsterListIntent.ClickErrorRetry()) } ) default: EmptyView() } }.onAppear { contract.dispatch(.OnInit()) } } }

Slide 213

Slide 213 text

40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract var body: some View { ZStack { switch contract.state { case _ as MonsterListState.Loading: LoadingIndicator() case let state as MonsterListState.Stable: MonsterListView(state: state, dispatch: contract.dispatch) case let state as MonsterListState.Error: FullScreenErrorView( error: state.error, onClickRetry: { contract.dispatch(MonsterListIntent.ClickErrorRetry()) } ) default: EmptyView() } }.onAppear { contract.dispatch(.OnInit()) } } }

Slide 214

Slide 214 text

40 ローディング リスト 初期エラー ページ ローディング ページエラー

Slide 215

Slide 215 text

StateMachine DSL

Slide 216

Slide 216 text

41 whenจ͕ଟ͍ɹ 1 ▶︎ s t a t e と i n t e n t と a c t i o n 増えていくと w h e n 文も二次関数的に増えていく 😱 ॲཧ͕ผΕ͍ͯΔ 2 ▶︎コードの流れ分かりにくい ▶︎バグ修正や追加開発難しくなる

Slide 217

Slide 217 text

42 PlantUML Diagram state { process { emit(MonsterListAction.Hoge) emit(MonsterListAction.Hoge) } reduce { MonsterListState.Hoge } }

Slide 218

Slide 218 text

43 StateMachine.kt → DSLΛ௨ͯ͠UMLΛMapܕʹอ࣋͢Δ StateMachineProcessor.kt → StateMachineͰ࡞ΒΕͨMapΛ࢖ͬͯProcessॲཧΛ൑அ͢Δ StateMachineReducer.kt → StateMachineͰ࡞ΒΕͨMapΛ࢖ͬͯReduceॲཧΛ൑அ͢Δ

Slide 219

Slide 219 text

43 StateMachine.kt → DSLΛ௨ͯ͠UMLΛMapܕʹอ࣋͢Δ StateMachineProcessor.kt → StateMachineͰ࡞ΒΕͨMapΛ࢖ͬͯProcessॲཧΛ൑அ͢Δ StateMachineReducer.kt → StateMachineͰ࡞ΒΕͨMapΛ࢖ͬͯReduceॲཧΛ൑அ͢Δ ref: https://github.com/fika-tech/Macaron/tree/main/macaron-statemachine

Slide 220

Slide 220 text

44 MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } }

Slide 221

Slide 221 text

# class MonsterListStateMachine( private val repository: MonsterRepository, ) : StateMachine( builder = { TODO() } ) MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 222

Slide 222 text

# class MonsterListStateMachine( private val repository: MonsterRepository, ) : StateMachine( builder = { TODO() } ) MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 223

Slide 223 text

# builder = { TODO() } MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 224

Slide 224 text

# MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading builder = { state { }a } // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 225

Slide 225 text

# MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading state { }a // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 226

Slide 226 text

# MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading state { process { } }a // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 227

Slide 227 text

# MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading state { process { loadMonsterList(0, repository, ::emit) } }a // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 228

Slide 228 text

# MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading state { process { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess(monsterList)) // or emit(MonsterListAction.LoadError(error)) } }a // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { emit(MonsterListAction.Loading) ... emit(MonsterListAction.LoadSuccess(...)) ... 44

Slide 229

Slide 229 text

# MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading state { process { loadMonsterList(0, repository, ::emit) } }a // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } 44

Slide 230

Slide 230 text

// Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit -> { loadMonsterList(0, repository, ::emit) } } # state { process { loadMonsterList(0, repository, ::emit) } reduce { } }a // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading 44

Slide 231

Slide 231 text

# state { process { loadMonsterList(0, repository, ::emit) } reduce { } }a MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } 44

Slide 232

Slide 232 text

# state { process { loadMonsterList(0, repository, ::emit) } reduce { MonsterListState.Loading } }a MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading A.Loading // Reducer is MonsterListState.Initial -> when (action) { is MonsterListAction.Loading -> { MonsterListState.Loading } } 44

Slide 233

Slide 233 text

45 class MonsterListStateMachine(private val repository: MonsterRepository) : StateMachine( builder = { state { process { loadMonsterList(0, repository, ::emit) } reduce { MonsterListState.Loading } } state { reduce { MonsterListState.Stable.List(action.monsterList) } reduce { MonsterListState.Error(action.error) } } state { process { emit(MonsterListAction.NavigateDetails(intent.monster)) } } state { process { loadMonsterList(state.currentOffset, repository, ::emit) } reduce { MonsterListState.Stable.PageLoading(state.monsterList) } } state { reduce { MonsterListState.Stable.List(state.monsterList + action.monsterList) } reduce { MonsterListState.Stable.PageError(state.monsterList, action.error) } } state { process { loadMonsterList(state.currentOffset, repository, ::emit) } reduce { MonsterListState.Stable.PageLoading(state.monsterList) } } state { process { loadMonsterList(0, repository, ::emit) } reduce { MonsterListState.Loading } } } )

Slide 234

Slide 234 text

46 val stateMachine = MonsterListStateMachine(monsterRepository = ...) val processor = StateMachineProcessor(stateMachine) val reducer = StateMachineReducer(stateMachine) Reducer Processor Store

Slide 235

Slide 235 text

47 whenจ͕ଟ͍ɹ 1 ▶︎ w h e n 文一個も必要なくなった ▶︎書くコードもかなり減った ॲཧ͕ผΕ͍ͯΔ 2 ▶︎ p r o c e s s o r と r e d u c e r の処理は同じ ブロックに入っているので、コードの 流れわかりやすくなった

Slide 236

Slide 236 text

·ͱΊ

Slide 237

Slide 237 text

# ͍ΖΜͳٕज़Ϩϕϧͷϝϯόʔ͕Δ 48

Slide 238

Slide 238 text

# ͍ΖΜͳٕज़Ϩϕϧͷϝϯόʔ͕Δ 48

Slide 239

Slide 239 text

# 2 ٕज़΋֮͑ͳ ͍ͱ͍͚ͳ͍ 1 Ҋ݅ͷυϝΠϯ ஌ࣝ΋֮͑ͳ͍ ͱ͍͚ͳ͍ 49

Slide 240

Slide 240 text

# 2 ٕज़΋֮͑ͳ ͍ͱ͍͚ͳ͍ 1 Ҋ݅ͷυϝΠϯ ஌ࣝ΋֮͑ͳ͍ ͱ͍͚ͳ͍ KMPͰٵऩ ·ͣ͸ Compose΍ SwiftUIʹूத 49

Slide 241

Slide 241 text

# Compose & SwiftUI ݖݶͷऔಘ PUSH௨஌ 50

Slide 242

Slide 242 text

UML͕࢓༷ॻʹͳΔ 1 ▶︎ U M L を見ながら S t a t e M a c h i n e も�� 実装しやすい ࣮૷͸ಉ࣌ฒߦͰ͖Δ 2 ▶︎ 最初に U M L と S t a t e と E n t i t y を用意 するだけで、アプリの画面も K M P 側の S t a t e M a c h i n e も同時に実装できる Kotlinͷ஌ࣝগͳͯ͘΋͍͍ 3 ▶︎ S t a t e M a c h i n e は D S L 化されている�� ので i O S エンジニアでも複雑すぎない 画面の S t a t e M a c h i n e を書けます খ͍͞࢓༷มߋʹ΋ ରԠ͠΍͍͢ 4 ▶︎ボタンなど追加する場合、 I n t e n t と A c t i o n を用意して p r o c e s s と r e d u c e を 書くだけで追加できます 51

Slide 243

Slide 243 text

52 ਂ͍Domain஌͕ࣝඞཁ 2 ▶︎ U M L を作る時に D o m a i n と U I の ロジックを落とし込むので D o m a i n 層や U I の動きの深い理解が必要 ྆OSͷ஌͕ࣝ๬·͍͠ 3 ▶︎ U M L を作成する時に両 O S の動きを 考えながら組む事が必要 ֶशίετ͕͋Δ 1 ▶︎ M V I や S t a t e M a c h i n e の考え方が 慣れていない方が多い େ͖͍࢓༷มߋʹऑ͍ 4 ▶︎仕様変更が大きければ、 S t a t e の� 構造を考え直すことがあります

Slide 244

Slide 244 text

Macaronʹ͍ͭͯ 53 0.1.0ϦϦʔε 1 ▶︎ドキュメンテーションまだ用意できてない ▶︎ A P I はこれから変わる可能性ある Roadmap 2 ▶︎ ドキュメンテーションの作成 ▶︎ M i d d l e w a r e / P l u g i n の仕組みを考え直す ▶︎ サンプルコードやアプリを増やす ▶︎ U M L からのコード生成 ▶︎ T i m e T r a v e l デバッグ https://github.com/fika-tech/Macaron

Slide 245

Slide 245 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠

Slide 246

Slide 246 text

54 Attributions: ▶︎ https://icons8.com/icons/collections/JLYaFSTqBIyi ▶︎ https://www.flaticon.com/free-icons/flow-chart ▶︎ https://www.flaticon.com/free-icon/uml_5687240 ▶︎ https://www.flaticon.com/free-icons/feet ▶︎ https://greenchess.net/ ▶︎ https://www.freepik.com/free-vector/scary-monster- set-colored-monsters-with-teeth-eyes-illustration- funny-monsters_13031939.htm ▶︎ https://www.freepik.com/free-vector/monsters-set- cartoon-cute-character-isolated-white- background_13031453.htm References: ▶︎ https://github.com/Tinder/StateMachine ▶︎ https://github.com/orbit-mvi/orbit-mvi ▶︎ https://github.com/arkivanov/MVIKotlin ▶︎ https://github.com/reduxjs/redux ▶︎ https://github.com/ReactorKit/ReactorKit ▶︎ https://github.com/kubode/reaktor Resources: ▶︎ https://developer.android.com/topic/architecture/ui-layer/state-production ▶︎ https://medium.com/swlh/mvi-architecture-with-android-fcde123e3c4a ▶︎ https://yuyakaido.hatenablog.com/entry/2017/12/12/235143 ▶︎ https://plantuml.com/ Links: ▶︎ https://github.com/fika-tech/Macaron ▶︎ https://github.com/fika-tech/DroigKaigi-Sample