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

MVIに基づくStateMachineアーキテクチャ:KMPとJetpack ComposeとSwiftUIを組み合わせる

MVIに基づくStateMachineアーキテクチャ:KMPとJetpack ComposeとSwiftUIを組み合わせる

DroidKaigi 2023:

目次:
- KMMとJetpack ComposeとSwiftUIの紹介
- MVIアーキテクチャ
- なぜMVIを使用するのか
- MVIの実装方法
- MVIを使って画面を実装する
- MVIに基くStateMachine
- Jetpack Compose & SwiftUI の導入
- Jetpack Compose上の使い方
- SwiftUI上の使い方
- StateMachine DSL
- StateMachine DSL を使ってリファクタリングする
- 開発上のメリット・デメリット

Marco Valentino

September 15, 2023
Tweet

More Decks by Marco Valentino

Other Decks in Programming

Transcript

  1. ςΫϊϩδʔͷ঺հ 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
  2. 6 ςΫϊϩδʔͷ঺հ ▶︎ コードを削減 ▶︎ 直感的 ▶︎ 開発を加速させる ▶︎ パワフル

    ▶︎ 高度なアニメーション制御 ▶︎ シンプルになったデータフロー ▶︎ A P I の拡 ▶︎ 新タイプのグラフとインタラクティブな機能 Jetpack Compose SwiftUI
  3. 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
  4. 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
  5. 12 macaron-core/../components/Store.kt interface Store<I : Intent, A : Action, S

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

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

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

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

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

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

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

    : State> { val state: StateFlow<S> val event: Flow<A?> fun dispatch(intent: I) fun process(event: A) fun dispose() fun collect( onState: (S) -> Unit, onEvent: (A?) -> Unit, ): Job } iOSଆʹKotlin FlowΛ collect͢Δϔϧύʔ 12
  13. 13 macaron-core/../components/Processor.kt interface Processor<I : Intent, A : Action, S

    : State> { suspend fun process(intent: I, state: S): Flow<A> } macaron-core/../components/Reducer.kt interface Reducer<A : Action, S : State> { suspend fun reduce(action: A, state: S): S }
  14. 14 macaron-core/../components/DefaultStore.kt class DefaultStore<I : Intent, A : Action, S

    : State>( initialState: S, private val processor: Processor<I, A, S>, private val reducer: Reducer<A, S>, coroutineContext: CoroutineContext, ) : Store<I, A, S> { private val scope: CoroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) private val intents: MutableSharedFlow<I> = MutableSharedFlow(Int.MAX_VALUE, Int.MAX_VALUE) private val _state: MutableStateFlow<S> = MutableStateFlow(initialState) private val _events: MutableStateFlow<List<Action.Event>> = MutableStateFlow(emptyList()) override val state: MutableStateFlow<S> = _state override val events: Flow<A?> = _events.map { it.firstOrNull() as A? } override val currentState: S get() = _state.value override fun dispatch(intent: I) { scope.launch { intents.emit(intent) } } override fun process(event: A) { scope.launch { _events.emit(_events.value.filterNot { it == event }) } } private suspend fun send(event: Action.Event) { _events.emit(_events.value + event) } override fun dispose() { scope.cancel() } ... }
  15. 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<I>(...)
  16. # macaron-core/../components/DefaultStore.kt private val intents = MutableSharedFlow<I>(...) 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
  17. 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<Monster> }
  18. # shared/../feature/monsterList/MonsterListState.kt sealed0class0MonsterListState0:0State0{0 data0object0Initial0:0MonsterListState()0 data0object0Loading0:0MonsterListState()0 sealed class0Stable :0MonsterListState() {1 abstract0val0monsterList:0List<Monster>0

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

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

    data0class0List( override0val0monsterList:0List<Monster> )0:0Stable()0 data0class0PageLoading( override0val0monsterList:0List<Monster> )0:0Stable()0 data0class0PageError( override0val0monsterList:0List<Monster>,0 val error:0Throwable )0:0Stable() }0 data0class0Error(val0error:0Throwable)0:0MonsterListState()0 }1 ページエラー Domain Contract Processor Reducer
  21. 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
  22. 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
  23. 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
  24. shared/../feature/monsterList/MonsterListAction.kt # sealed class MonsterListAction : Action { data object

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

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

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

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

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

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

    Loading : MonsterListAction() data class LoadSuccess( val monsterList: List<Monster> ) : MonsterListAction() data class LoadError( val error: Throwable ) : MonsterListAction() data class NavigateDetails( val monster: Monster ) : MonsterListAction(), Action.Event } Domain Contract Processor Reducer 詳細 21
  31. 22 shared/../feature/monsterList/MonsterProcessor.kt class MonsterListProcessor( private val repository: MonsterRepository, ) :

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

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

    Processor<MonsterListIntent, MonsterListAction, MonsterListState> { override suspend fun process( intent: MonsterListIntent, state: MonsterListState, ): Flow<MonsterListAction> = flow { when (intent) { isaMonsterListIntent.OnInita-> TODO() isaMonsterListIntent.ClickItema-> TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a } } Domain Contract Processor Reducer 22
  34. # 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
  35. # 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
  36. # 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
  37. # 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
  38. # shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema->

    TODO() isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22
  39. # 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
  40. # shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema->

    { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22
  41. # 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
  42. # 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
  43. # 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
  44. # 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
  45. # 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
  46. # shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema->

    { ... } isaMonsterListIntent.ClickErrorRetrya-> TODO() isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22
  47. # 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
  48. # 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
  49. # 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
  50. # 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
  51. # 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
  52. # 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
  53. # shared/../feature/monsterList/MonsterProcessor.kt when (intent) { isaMonsterListIntent.OnInita-> { ... } isaMonsterListIntent.ClickItema->

    { ... } isaMonsterListIntent.ClickErrorRetrya-> { ... } isaMonsterListIntent.OnScrollToBottoma-> TODO() }a Domain Contract Processor Reducer 22
  54. # 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
  55. # 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
  56. # 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
  57. # 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
  58. # 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
  59. 23 shared/../feature/monsterList/MonsterReducer.kt class MonsterListReducer : Reducer<MonsterListAction, MonsterListState> { 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
  60. # shared/../feature/monsterList/MonsterReducer.kt class MonsterListReducer : Reducer<MonsterListAction, MonsterListState> { 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
  61. # 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
  62. # 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
  63. # 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
  64. # 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
  65. # 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
  66. # 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
  67. # 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
  68. # 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
  69. # 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
  70. # 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
  71. # 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
  72. # 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
  73. # 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
  74. # 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
  75. # 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
  76. # 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
  77. # 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
  78. # 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
  79. # 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
  80. # 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
  81. # 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
  82. # 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
  83. # 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
  84. 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
  85. # 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
  86. # 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
  87. # 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
  88. # 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
  89. # 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
  90. # 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
  91. # 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
  92. # 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
  93. # 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
  94. # 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
  95. # 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
  96. # 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
  97. State Machineͱ͸ʁ # ݻମ on: ༥ղ transition: ӷମ ӷମ on:

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

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

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

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

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

    transition: 🙅 ӷମ on: ڽݻ transition: ݸମ on: ؾԽ transition: ؾମ ؾମ on: ڽॖ(ӷԽ) transition: ӷମ 29
  103. 30 MVIΛجͮ͘StateMachine // Processor when (intent) { is MonsterListIntent ->

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

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

    when (state) { is MonsterListState -> { ... } } } // Reducer when (action) { is MonsterAction -> when (state) { is MonsterListState -> { ... } } } 30
  106. # 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
  107. # 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
  108. 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 ... } ... }
  109. # 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
  110. 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 ... } ... }
  111. 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
  112. 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
  113. 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
  114. 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
  115. 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
  116. 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
  117. # 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
  118. # 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
  119. # 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
  120. 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
  121. 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
  122. 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) }
  123. # 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
  124. 36 androidApp/../ui/screen/MonsterListScreen.kt state.render<MonsterListState.Loading>a{ ... } state.render<MonsterListState.Stable>a{ 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<MonsterListState.Stable.PageLoading>a{ item { PageLoadingIndicatorItem() } } state.renderItems<MonsterListState.Stable.PageError>a{ item { PageErrorItem(error = it.error, onClickRetry = { dispatch(MonsterListIntent.ClickErrorRetry) }) } } } } }
  125. # androidApp/../ui/screen/MonsterListScreen.kt state.render<MonsterListState.Loading>a{ ... } state.render<MonsterListState.Stable>a{ 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<MonsterListState.Stable.PageLoading>a{ item { PageLoadingIndicatorItem() } } state.renderItems<MonsterListState.Stable.PageError>a{ 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
  126. # androidApp/../ui/screen/MonsterListScreen.kt state.render<MonsterListState.Loading> { ... } state.render<MonsterListState.Stable> { 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<MonsterListState.Stable.PageLoading> { item { PageLoadingIndicatorItem() } } state.renderItems<MonsterListState.Stable.PageError> { 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
  127. # androidApp/../ui/screen/MonsterListScreen.kt state.render<MonsterListState.Loading> { ... } state.render<MonsterListState.Stable> { 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<MonsterListState.Stable.PageLoading> { item { PageLoadingIndicatorItem() } } state.renderItems<MonsterListState.Stable.PageError> { 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
  128. 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
  129. 38 androidApp/../core/Contract.kt data class Contract<I : Intent, A : Action,

    S : State>( val state: S, val dispatch: (I) -> Unit = {}, val event: A? = null, val process: (A) -> Unit = {}, ) @Composable fun <I : Intent, A : Action, S : State> contract( store: Store<I, A, S>, ): Contract<I, A, S> { val state by store.state.collectAsState() val event by store.event.collectAsState(initial = null) return Contract(state, event, store::dispatch, store::process) }
  130. 39 iosApp/../Core/Contract.swift open class Contract<I: Intent, A: Action, S: shared.State>:

    ObservableObject { @Published public private(set) var state: S public let dispatch: (I) -> Void public var events: some Publisher<A, Never> { eventSubject } private let eventSubject = PassthroughSubject<A, Never>() private var cancellables: Set<AnyCancellable> = [] public init(store: Store) { self.state = store.currentState as! S self.dispatch = { store.dispatch(intent: $0) } AnyCancellable { store.dispose() }.store(in: &cancellables) store.collect { [weak self] newState in if let self, let newState = newState as? S { self.state = newState } } onEvent: { [eventSubject, store] event in if let action = event as? A { store.process(event: action) eventSubject.send(action) } } } }
  131. 39 iosApp/../Core/Contract.swift open class Contract<I: Intent, A: Action, S: shared.State>:

    ObservableObject { @Published public private(set) var state: S public let dispatch: (I) -> Void public var events: some Publisher<A, Never> { eventSubject } private let eventSubject = PassthroughSubject<A, Never>() private var cancellables: Set<AnyCancellable> = [] public init(store: Store) { self.state = store.currentState as! S self.dispatch = { store.dispatch(intent: $0) } AnyCancellable { store.dispose() }.store(in: &cancellables) store.collect { [weak self] newState in if let self, let newState = newState as? S { self.state = newState } } onEvent: { [eventSubject, store] event in if let action = event as? A { store.process(event: action) eventSubject.send(action) } } } }
  132. 39 iosApp/../Core/Contract.swift open class Contract<I: Intent, A: Action, S: shared.State>:

    ObservableObject { @Published public private(set) var state: S public let dispatch: (I) -> Void public var events: some Publisher<A, Never> { eventSubject } private let eventSubject = PassthroughSubject<A, Never>() private var cancellables: Set<AnyCancellable> = [] public init(store: Store) { self.state = store.currentState as! S self.dispatch = { store.dispatch(intent: $0) } AnyCancellable { store.dispose() }.store(in: &cancellables) store.collect { [weak self] newState in if let self, let newState = newState as? S { self.state = newState } } onEvent: { [eventSubject, store] event in if let action = event as? A { store.process(event: action) eventSubject.send(action) } } } }
  133. 40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract<MonsterListIntent,

    MonsterListAction, MonsterListState> 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()) } } }
  134. 40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract<MonsterListIntent,

    MonsterListAction, MonsterListState> 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()) } } }
  135. 40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract<MonsterListIntent,

    MonsterListAction, MonsterListState> 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()) } } }
  136. 40 iosApp/../UI/Screen/MonsterListScreen.swift struct MonsterListScreen: View { @ObservedObject var contract: Contract<MonsterListIntent,

    MonsterListAction, MonsterListState> 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()) } } }
  137. 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 ▶︎コードの流れ分かりにくい ▶︎バグ修正や追加開発難しくなる
  138. 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) } }
  139. # class MonsterListStateMachine( private val repository: MonsterRepository, ) : StateMachine<MonsterListIntent,

    MonsterListAction, MonsterListState>( 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
  140. # class MonsterListStateMachine( private val repository: MonsterRepository, ) : StateMachine<MonsterListIntent,

    MonsterListAction, MonsterListState>( 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
  141. # 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
  142. # MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading

    A.Loading builder = { state<MonsterListState.Initial> { }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
  143. # MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading

    A.Loading state<MonsterListState.Initial> { }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
  144. # MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading

    A.Loading state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { } }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
  145. # MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading

    A.Loading state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { 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
  146. # MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading

    A.Loading state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { 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
  147. # MonsterListState Initial I.OnInit > A.Loading, [A.LoadSuccess | A.LoadError] Loading

    A.Loading state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { 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
  148. // Processor is MonsterListState.Initial -> when (intent) { is MonsterListIntent.OnInit

    -> { loadMonsterList(0, repository, ::emit) } } # state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { loadMonsterList(0, repository, ::emit) } reduce<MonsterListAction.Loading> { } }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
  149. # state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { loadMonsterList(0, repository, ::emit) } reduce<MonsterListAction.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
  150. # state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { loadMonsterList(0, repository, ::emit) } reduce<MonsterListAction.Loading>

    { 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
  151. 45 class MonsterListStateMachine(private val repository: MonsterRepository) : StateMachine<MonsterListIntent, MonsterListAction, MonsterListState>(

    builder = { state<MonsterListState.Initial> { process<MonsterListIntent.OnInit> { loadMonsterList(0, repository, ::emit) } reduce<MonsterListAction.Loading> { MonsterListState.Loading } } state<MonsterListState.Loading> { reduce<MonsterListAction.LoadSuccess> { MonsterListState.Stable.List(action.monsterList) } reduce<MonsterListAction.LoadError> { MonsterListState.Error(action.error) } } state<MonsterListState.Stable> { process<MonsterListIntent.ClickItem> { emit(MonsterListAction.NavigateDetails(intent.monster)) } } state<MonsterListState.Stable.List> { process<MonsterListIntent.OnScrollToBottom> { loadMonsterList(state.currentOffset, repository, ::emit) } reduce<MonsterListAction.Loading> { MonsterListState.Stable.PageLoading(state.monsterList) } } state<MonsterListState.Stable.PageLoading> { reduce<MonsterListAction.LoadSuccess> { MonsterListState.Stable.List(state.monsterList + action.monsterList) } reduce<MonsterListAction.LoadError> { MonsterListState.Stable.PageError(state.monsterList, action.error) } } state<MonsterListState.Stable.PageError> { process<MonsterListIntent.ClickErrorRetry> { loadMonsterList(state.currentOffset, repository, ::emit) } reduce<MonsterListAction.Loading> { MonsterListState.Stable.PageLoading(state.monsterList) } } state<MonsterListState.Error> { process<MonsterListIntent.ClickErrorRetry> { loadMonsterList(0, repository, ::emit) } reduce<MonsterListAction.Loading> { MonsterListState.Loading } } } )
  152. 46 val stateMachine = MonsterListStateMachine(monsterRepository = ...) val processor =

    StateMachineProcessor(stateMachine) val reducer = StateMachineReducer(stateMachine) Reducer Processor Store
  153. 47 whenจ͕ଟ͍ɹ 1 ▶︎ w h e n 文一個も必要なくなった ▶︎書くコードもかなり減った

    ॲཧ͕ผΕ͍ͯΔ 2 ▶︎ p r o c e s s o r と r e d u c e r の処理は同じ ブロックに入っているので、コードの 流れわかりやすくなった
  154. 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
  155. 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 の� 構造を考え直すことがあります
  156. 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
  157. 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