Android における Model-View-Intent アーキテクチャ

Android における Model-View-Intent アーキテクチャ

Video: https://youtu.be/MIV7Mi9zko8

Android上の開発は非同期の扱いを避ける事はできないです。ネットワーク、フレームワーク、ユーザの操作などから非同期処理が発生してます。油断してしまうとアプリが複雑化してメンテナンスが難しくなります。皆様はネットワークからレスポンスを待っている途中、ユーザが画面をローテーションしても問題ないですか?並行してユーザがいろんな操作しても大丈夫でしょうか?

Model-View-Intent アーキテクチャは非同期処理が発生する前提で考えられたため、すべてがストリームとして扱ってデータの流れを一方通行にするかつ不変オブジェクトを使うのが MVI アーキテクチャの方針です。マルチスレッディングやAndroidのライフサイクルの対応から生じる問題がアーキテクチャによって解決されるおかげでアプリのロジックに集中できるようになり、コードが書きやすく、今後の保守も楽になります!

皆様が MVI アーキテクチャの強みを理解し自分で実現できるようにする目標で Kotlin と RxJava を使って並行処理、画面ローテーション、スナックバーを含んだ画面をどうやって作っていくかを語るつもりです!

05162bc961c3654218bf1839974a4f35?s=128

Benoît Quenaudon

February 08, 2018
Tweet

Transcript

  1. Android ʹ͓͚Δ ʊModel-View-Intentʊ ΞʔΩςΫνϟ Benoît Quenaudon @oldergod

  2. None
  3. “What if the user was a function?” by Andre Staltz

  4. “What if the user was a function?” by Andre Staltz

  5. user()

  6. intent(user())

  7. model(intent(user()))

  8. view(model(intent(user())))

  9. user(view(model(intent(user()))))

  10. view(model(intent()))

  11. None
  12. USER

  13. sealed class TasksIntent { }@

  14. sealed class TasksIntent { object InitialIntent : TasksIntent() }@

  15. None
  16. sealed class TasksIntent { object InitialIntent : TasksIntent() object RefreshIntent

    : TasksIntent() }@
  17. None
  18. sealed class TasksIntent { object InitialIntent : TasksIntent() object RefreshIntent

    : TasksIntent() data class ActivateTaskIntent(val task: Task) : TasksIntent() data class CompleteTaskIntent(val task: Task) : TasksIntent() }@
  19. None
  20. sealed class TasksIntent { object InitialIntent : TasksIntent() object RefreshIntent

    : TasksIntent() data class ActivateTaskIntent(val task: Task) : TasksIntent() data class CompleteTaskIntent(val task: Task) : TasksIntent() object ClearCompletedTasksIntent : TasksIntent() }@
  21. None
  22. sealed class TasksIntent { object InitialIntent : TasksIntent() object RefreshIntent

    : TasksIntent() data class ActivateTaskIntent(val task: Task) : TasksIntent() data class CompleteTaskIntent(val task: Task) : TasksIntent() object ClearCompletedTasksIntent : TasksIntent() data class ChangeFilterIntent(val filterType: TasksFilterType) : TasksIntent() }@
  23. sealed class TasksIntent { object InitialIntent : TasksIntent() object RefreshIntent

    : TasksIntent() data class ActivateTaskIntent(val task: Task) : TasksIntent() data class CompleteTaskIntent(val task: Task) : TasksIntent() object ClearCompletedTasksIntent : TasksIntent() data class ChangeFilterIntent(val filterType: TasksFilterType) : TasksIntent() }@
  24. class TasksFragment fun intents(): Observable<TasksIntent> { }@ }@

  25. class TasksFragment fun intents(): Observable<TasksIntent> { return initialIntent() }@a private

    fun initialIntent(): Observable<InitialIntent> { return Observable.just(InitialIntent) } }@
  26. class TasksFragment fun_intents():_Observable<TasksIntent> { return Observable.merge(initialIntent(), refreshIntent()) }@a private fun

    refreshIntent(): Observable<RefreshIntent> { return RxSwipeRefreshLayout.refreshes(swipeRefreshLayout) .map { RefreshIntent } }@ }@
  27. class TasksFragment fun_intents():_Observable<TasksIntent> { return Observable.merge(initialIntent(), refreshIntent(), completeTaskIntent(), activateTaskIntent(), clearCompletedTaskIntent(),

    changeFilterIntent()) }@ }@
  28. USER INTENTS Intent

  29. intents // Observable<TasksIntent>

  30. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

    Observable<TasksAction>
  31. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> is RefreshIntent -> is ActivateTaskIntent -> is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  32. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> is RefreshIntent -> is ActivateTaskIntent -> is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  33. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> is RefreshIntent -> is ActivateTaskIntent -> is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  34. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> is ActivateTaskIntent -> is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  35. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  36. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> ActivateTaskAction(intent.task) is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  37. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> ActivateTaskAction(intent.task) is CompleteTaskIntent -> CompleteTaskAction(intent.task) is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  38. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> ActivateTaskAction(intent.task) is CompleteTaskIntent -> CompleteTaskAction(intent.task) is ClearCompletedTasksIntent -> ClearCompletedTasksAction is ChangeFilterIntent -> }@
  39. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> ActivateTaskAction(intent.task) is CompleteTaskIntent -> CompleteTaskAction(intent.task) is ClearCompletedTasksIntent -> ClearCompletedTasksAction is ChangeFilterIntent -> LoadAndFilterTasksAction(intent.filterType) }@
  40. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> ActivateTaskAction(intent.task) is CompleteTaskIntent -> CompleteTaskAction(intent.task) is ClearCompletedTasksIntent -> ClearCompletedTasksAction is ChangeFilterIntent -> LoadAndFilterTasksAction(intent.filterType) }@
  41. private TasksAction actionFromIntent(MviIntent intent) { if (intent instanceof InitialIntent) {

    return LoadTasks.loadAndFilter(true, TasksFilterType.ALL_TASKS); } if (intent instanceof ChangeFilterIntent) { return LoadTasks.loadAndFilter(false, ((ChangeFilterIntent) intent).filterType()); } if (intent instanceof RefreshIntent) { return LoadTasks.load(((RefreshIntent) intent).forceUpdate()); } if (intent instanceof ActivateTaskIntent) { return ActivateTaskAction.create(((ActivateTaskIntent) intent).task()); } if (intent instanceof CompleteTaskIntent) { return CompleteTaskAction.create(((CompleteTaskIntent) intent).task()); } if (intent instanceof ClearCompletedTasksIntent) { return ClearCompletedTasksAction.create(); } throw new IllegalArgumentException("do not know how to treat this intent " + intent); }
  42. private TasksAction actionFromIntent(MviIntent intent) { if (intent instanceof InitialIntent) {

    return LoadTasks.loadAndFilter(true, TasksFilterType.ALL_TASKS); } if (intent instanceof ChangeFilterIntent) { return LoadTasks.loadAndFilter(false, ((ChangeFilterIntent) intent).filterType()); } if (intent instanceof RefreshIntent) { return LoadTasks.load(((RefreshIntent) intent).forceUpdate()); } if (intent instanceof ActivateTaskIntent) { return ActivateTaskAction.create(((ActivateTaskIntent) intent).task()); } if (intent instanceof CompleteTaskIntent) { return CompleteTaskAction.create(((CompleteTaskIntent) intent).task()); } if (intent instanceof ClearCompletedTasksIntent) { return ClearCompletedTasksAction.create(); } throw new IllegalArgumentException("do not know how to treat this intent " + intent); }
  43. private TasksAction actionFromIntent(MviIntent intent) { if (intent instanceof InitialIntent) {

    return LoadTasks.loadAndFilter(true, TasksFilterType.ALL_TASKS); } if (intent instanceof ChangeFilterIntent) { return LoadTasks.loadAndFilter(false, ((ChangeFilterIntent) intent).filterType()); } if (intent instanceof RefreshIntent) { return LoadTasks.load(((RefreshIntent) intent).forceUpdate()); } if (intent instanceof ActivateTaskIntent) { return ActivateTaskAction.create(((ActivateTaskIntent) intent).task()); } if (intent instanceof CompleteTaskIntent) { return CompleteTaskAction.create(((CompleteTaskIntent) intent).task()); } if (intent instanceof ClearCompletedTasksIntent) { return ClearCompletedTasksAction.create(); } throw new IllegalArgumentException("do not know how to treat this intent " + intent); }
  44. private TasksAction actionFromIntent(MviIntent intent) { if (intent instanceof InitialIntent) {

    return LoadTasks.loadAndFilter(true, TasksFilterType.ALL_TASKS); } if (intent instanceof ChangeFilterIntent) { return LoadTasks.loadAndFilter(false, ((ChangeFilterIntent) intent).filterType()); } if (intent instanceof RefreshIntent) { return LoadTasks.load(((RefreshIntent) intent).forceUpdate()); } if (intent instanceof ActivateTaskIntent) { return ActivateTaskAction.create(((ActivateTaskIntent) intent).task()); } if (intent instanceof CompleteTaskIntent) { return CompleteTaskAction.create(((CompleteTaskIntent) intent).task()); } if (intent instanceof ClearCompletedTasksIntent) { return ClearCompletedTasksAction.create(); } throw new IllegalArgumentException("do not know how to treat this intent " + intent); }
  45. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> ActivateTaskAction(intent.task) is CompleteTaskIntent -> CompleteTaskAction(intent.task) is ClearCompletedTasksIntent -> ClearCompletedTasksAction is ChangeFilterIntent -> LoadAndFilterTasksAction(intent.filterType) }@
  46. sealed class TasksAction { data class LoadAndFilterTasksAction(val filterType: TasksFilterType) :

    TasksAction() object LoadTasksAction : TasksAction() data class ActivateTaskAction(val task: Task) : TasksAction() data class CompleteTaskAction(val task: Task) : TasksAction() object ClearCompletedTasksAction : TasksAction() }@
  47. USER INTENTS Intent Action INTENT INTERPRETOR

  48. Action Load and Filter Tasks Logic Clear Completed Task Logic

    Complete Task Logic Activate Task Logic Load Tasks Logic Result
  49. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    }c
  50. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    }c
  51. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> }b }c
  52. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge()a }b }c
  53. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor) )a }b }c
  54. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor), shared.ofType(LoadAndFilterTasksAction::class.java).compose(loadAndFilterTasksProcessor) )a }b }c
  55. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor), shared.ofType(LoadAndFilterTasksAction::class.java).compose(loadAndFilterTasksProcessor), shared.ofType(ActivateTaskAction::class.java).compose(activateTaskProcessor) )a }b }c
  56. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor), shared.ofType(LoadAndFilterTasksAction::class.java).compose(loadAndFilterTasksProcessor), shared.ofType(ActivateTaskAction::class.java).compose(activateTaskProcessor), shared.ofType(ClearCompletedTasksAction::class.java).compose(clearCompletedTasksProcessor) )a }b }c
  57. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor), shared.ofType(LoadAndFilterTasksAction::class.java).compose(loadAndFilterTasksProcessor), shared.ofType(ActivateTaskAction::class.java).compose(activateTaskProcessor), shared.ofType(ClearCompletedTasksAction::class.java).compose(clearCompletedTasksProcessor), shared.ofType(CompleteTaskAction::class.java).compose(completeTaskProcessor) )a }b }c
  58. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor), shared.ofType(LoadAndFilterTasksAction::class.java).compose(loadAndFilterTasksProcessor), shared.ofType(ActivateTaskAction::class.java).compose(activateTaskProcessor), shared.ofType(ClearCompletedTasksAction::class.java).compose(clearCompletedTasksProcessor), shared.ofType(CompleteTaskAction::class.java).compose(completeTaskProcessor) )a }b }c
  59. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> }@ }@
  60. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) }@ }@
  61. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) .map { tasks -> LoadTasksResult.Success(tasks) } }@ }@
  62. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) .map { tasks -> LoadTasksResult.Success(tasks) } .onErrorReturn { t -> loadTasksResult.Failure(t) } }@ }@
  63. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) .map { tasks -> LoadTasksResult.Success(tasks) } .onErrorReturn { t -> loadTasksResult.Failure(t) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) }@ }@
  64. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) .map { tasks -> LoadTasksResult.Success(tasks) } .onErrorReturn { t -> loadTasksResult.Failure(t) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) }@ }@
  65. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

    tasksRepository.getTasks() // Observable<List<Tasks>> .map { tasks -> LoadTasksResult.Success(tasks) } .onErrorReturn { t -> loadTasksResult.Failure(t) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .startWith(LoadTasksResult.InFlight) }@ }@
  66. var actionProcessor: ObservableTransformer<TasksAction, TasksResult> = ObservableTransformer { actions: Observable<TasksAction> ->

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor), shared.ofType(LoadAndFilterTasksAction::class.java).compose(loadAndFilterTasksProcessor), shared.ofType(ActivateTaskAction::class.java).compose(activateTaskProcessor), shared.ofType(ClearCompletedTasksAction::class.java).compose(clearCompletedTasksProcessor), shared.ofType(CompleteTaskAction::class.java).compose(completeTaskProcessor) )a }b }c
  67. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

    Observable<TasksAction> .compose(actionProcessor) // Observable<TasksResult>
  68. USER INTENTS Intent Result Action INTENT INTERPRETOR PROCESSOR REPOSITORY

  69. θϩ͔Βը໘Λ࡞Δʹ͸ʁ

  70. data class TasksViewState(z )@

  71. data class TasksViewState(z val isLoading: Boolean )@

  72. data class TasksViewState(z val isLoading: Boolean, val tasksFilterType: TasksFilterType )@

  73. data class TasksViewState(z val isLoading: Boolean, val tasksFilterType: TasksFilterType, val

    tasks: List<Task> )@
  74. data class TasksViewState(z val isLoading: Boolean, val tasksFilterType: TasksFilterType, val

    tasks: List<Task>, val error: Throwable? )@
  75. data class TasksViewState(z val isLoading: Boolean, val tasksFilterType: TasksFilterType, val

    tasks: List<Task>, val error: Throwable?, val taskComplete: Boolean, val taskActivated: Boolean, val completedTasksCleared: Boolean )@
  76. data class TasksViewState(z val isLoading: Boolean, val tasksFilterType: TasksFilterType, val

    tasks: List<Task>, val error: Throwable?, val taskComplete: Boolean, val taskActivated: Boolean, val completedTasksCleared: Boolean )@
  77. data class TasksViewState(z val isLoading: Boolean, val tasksFilterType: TasksFilterType, val

    tasks: List<Task>, val error: Throwable?, val taskComplete: Boolean, val taskActivated: Boolean, val completedTasksCleared: Boolean )@{ companion object Factory { fun default() = TasksViewState( isLoading = false, tasksFilterType = ALL_TASKS, tasks = emptyList(), error = null, taskComplete = false, taskActivated = false, completedTasksCleared = false) } }
  78. New State Result Default State

  79. New State REDUCER Result Default State

  80. New State REDUCER Result Previous State

  81. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

    Observable<TasksAction> .compose(actionProcessor) // Observable<TasksResult> .scan(TasksViewState.default(), reducer) // Observable<TasksViewState>
  82. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> }e
  83. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> }e
  84. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> when (result)_{ is LoadTasksResult -> /***/ is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  85. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> when (result)_{ is LoadTasksResult -> { when (result) { is LoadTasksResult.InFlight -> /***/ is LoadTasksResult.Failure -> /***/ is LoadTasksResult.Success -> /***/ }b }c is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  86. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> when (result)_{ is LoadTasksResult -> { when (result) { is LoadTasksResult.InFlight -> previousState.copy(isLoading = true) is LoadTasksResult.Failure -> /***/ is LoadTasksResult.Success -> /***/ }b }c is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  87. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> when (result)_{ is LoadTasksResult -> { when (result) { is LoadTasksResult.InFlight -> previousState.copy(isLoading = true) is LoadTasksResult.Failure -> previousState.copy(isLoading = false, error = result.error) is LoadTasksResult.Success -> /***/ }b }c is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  88. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> when (result)_{ is LoadTasksResult -> { when (result) { is LoadTasksResult.InFlight -> previousState.copy(isLoading = true) is LoadTasksResult.Failure -> previousState.copy(isLoading = false, error = result.error) is LoadTasksResult.Success -> { previousState.copy(isLoading = false, tasks = result.tasks) }a }b }c is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  89. val reducer = BiFunction<TasksViewState, TasksResult, TasksViewState> { previousState: TasksViewState, result:

    TasksResult -> when (result)_{ is LoadTasksResult -> { when (result) { is LoadTasksResult.InFlight -> previousState.copy(isLoading = true) is LoadTasksResult.Failure -> previousState.copy(isLoading = false, error = result.error) is LoadTasksResult.Success -> { previousState.copy(isLoading = false, tasks = result.tasks) }a }b }c is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  90. USER INTENTS Intent Result Action State INTENT INTERPRETOR REDUCER PROCESSOR

    REPOSITORY
  91. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

    Observable<TasksAction> .compose(actionProcessor) // Observable<TasksResult> .scan(TasksViewState.default(), reducer) // Observable<TasksViewState>
  92. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

    Observable<TasksAction> .compose(actionProcessor) // Observable<TasksResult> .scan(TasksViewState.default(), reducer) // Observable<TasksViewState> .subscribe { state -> render(state) }
  93. fun render(state: TasksViewState) { }u

  94. fun render(state: TasksViewState) { swipeRefreshLayout.isRefreshing = state.isLoading }u

  95. fun render(state: TasksViewState) { swipeRefreshLayout.isRefreshing = state.isLoading if (state.error !=

    null) { showLoadingTasksError() return }a }u
  96. fun render(state: TasksViewState) { swipeRefreshLayout.isRefreshing = state.isLoading if (state.error !=

    null) { showLoadingTasksError() return }a if (state.taskActivated) { showMessage(getString(R.string.task_marked_active)) }b if (state.taskComplete) { showMessage(getString(R.string.task_marked_complete)) }c if (state.completedTasksCleared) { showMessage(getString(R.string.completed_tasks_cleared)) }d }u
  97. if (state.taskActivated) { showMessage(getString(R.string.task_marked_active)) }b if (state.taskComplete) { showMessage(getString(R.string.task_marked_complete)) }c

    if (state.completedTasksCleared) { showMessage(getString(R.string.completed_tasks_cleared)) }d if (state.tasks.isEmpty()) { when (state.tasksFilterType) {z ACTIVE_TASKS -> showNoActiveTasks() COMPLETED_TASKS -> showNoCompletedTasks() ALL_TASKS -> showNoTasks() }e }_ }u
  98. showMessage(getString(R.string.completed_tasks_cleared)) }d if (state.tasks.isEmpty()) { when (state.tasksFilterType) {z ACTIVE_TASKS ->

    showNoActiveTasks() COMPLETED_TASKS -> showNoCompletedTasks() ALL_TASKS -> showNoTasks() }e }_else { listAdapter.replaceData(state.tasks) tasksView.visibility = View.VISIBLE noTasksView.visibility = View.GONE when (state.tasksFilterType) { ACTIVE_TASKS -> showActiveFilterLabel() COMPLETED_TASKS -> showCompletedFilterLabel() ALL_TASKS -> showAllFilterLabel() }g }h }u
  99. fun render(state: TasksViewState) { swipeRefreshLayout.isRefreshing = state.isLoading if (state.error !=

    null) { showLoadingTasksError() return }a if (state.taskActivated) { showMessage(getString(R.string.task_marked_active)) }b if (state.taskComplete) { showMessage(getString(R.string.task_marked_complete)) }c if (state.completedTasksCleared) { showMessage(getString(R.string.completed_tasks_cleared)) }d if (state.tasks.isEmpty()) { when (state.tasksFilterType) { ACTIVE_TASKS -> showNoActiveTasks() COMPLETED_TASKS -> showNoCompletedTasks() ALL_TASKS -> showNoTasks() }e }_else { listAdapter.replaceData(state.tasks) tasksView.visibility = View.VISIBLE noTasksView.visibility = View.GONE when (state.tasksFilterType) { ACTIVE_TASKS -> showActiveFilterLabel() COMPLETED_TASKS -> showCompletedFilterLabel() ALL_TASKS -> showAllFilterLabel() }g }h }u
  100. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY
  101. effects sidS Side effects

  102. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY
  103. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY
  104. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY
  105. USER Intent Result Action State INTENT INTERPRETOR REDUCER REPOSITORY INTENTS

    RENDER PROCESSOR
  106. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY Unidirectional data flow
  107. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY
  108. USER USER INTERFACE INTENTS RENDER Intent Result Action State INTENT

    INTERPRETOR REDUCER PROCESSOR REPOSITORY
  109. USER USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action

    State INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  110. interface TasksUi { fun render(state: TasksViewState) fun intents(): Observable<TasksIntent> }@

    interface TasksViewModel { fun processIntents(intents: Observable<TasksIntent>) fun states(): Observable<TasksViewState> }@
  111. interface TasksUi { fun render(state: TasksViewState) fun intents(): Observable<TasksIntent> }@

    interface TasksViewModel { fun processIntents(intents: Observable<TasksIntent>) fun states(): Observable<TasksViewState> }@
  112. interface TasksUi { fun render(state: TasksViewState) fun intents(): Observable<TasksIntent> }@

    interface TasksViewModel { fun processIntents(intents: Observable<TasksIntent>) fun states(): Observable<TasksViewState> }@
  113. ฼͔Βͷ͓ి࿩Ͱ͢

  114. User: initialIntent() App: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: you.onStop()
  115. User: initialIntent() App: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: you.onStop()
  116. User: initialIntent() App: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: you.onStop()
  117. User: initialIntent() App: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: app.onStop()
  118. USER USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action

    State INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY C B A
  119. INTENTS RENDER VIEW MODEL Intent Result Action State INTENT INTERPRETOR

    REDUCER PROCESSOR REPOSITORY B C A USER USER INTERFACE B C A
  120. USER USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action

    State INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY A
  121. Config change ΤϒϦ΢ΣΞ

  122. None
  123. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  124. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER

  125. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER

  126. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT

  127. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT

  128. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT

  129. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT .autoConnect(0)

  130. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER .autoConnect(0) SUBJECT

  131. VIEW MODEL .autoConnect(0) SUBJECT

  132. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER .autoConnect(0) SUBJECT

  133. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER .replay(1) .autoConnect(0) SUBJECT

  134. class TasksViewModel : ViewModel() { fun processIntents(intents: Observable<TasksIntent>) { }b

    fun states(): Observable<TasksViewState> { }c }e
  135. class TasksViewModel : ViewModel() { private val intentsSubject = PublishSubject.create<TasksIntent>()

    fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b fun states(): Observable<TasksViewState> { }c }e
  136. class TasksViewModel : ViewModel() { private val intentsSubject = PublishSubject.create<TasksIntent()

    val states: Observable<TasksViewState> = compose() fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b private fun compose(): Observable<TasksViewState> { }c }e
  137. class TasksViewModel : ViewModel() { private val intentsSubject = PublishSubject.create<TasksIntent>()

    val states: Observable<TasksViewState> = compose() fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b private fun compose(): Observable<TasksViewState> { return intentsSubject .map { intent -> actionFromIntent(intent) } .compose(actionProcessor) .scan(TasksViewState.default(), reducer) }c }e
  138. class TasksViewModel : ViewModel() { private val intentsSubject = PublishSubject.create<TasksIntent>()

    val states: Observable<TasksViewState> = compose() fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b private fun compose(): Observable<TasksViewState> { return intentsSubject .map { intent -> actionFromIntent(intent) } .compose(actionProcessor) .scan(TasksViewState.default(), reducer) .replay(1) .autoConnect(0) }c }e
  139. class TasksViewModel : ViewModel() { private val intentsSubject = PublishSubject.create<TasksIntent>()

    val states: Observable<TasksViewState> = compose() fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b private fun compose(): Observable<TasksViewState> { return intentsSubject .map { intent -> actionFromIntent(intent) } .compose(actionProcessor) .scan(TasksViewState.default(), reducer) .replay(1) .autoConnect(0) }c }e
  140. class TasksFragment : Fragment() { fun intents(): Observable<TasksIntent> { /***/

    } fun render(state: TasksViewState) { /***/ } }d
  141. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } }d
  142. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } override fun onStart() { viewModel.states().subscribe(this::render) }b }d
  143. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a private val disposables = CompositeDisposable()a fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } override fun onStart() { disposables.add(viewModel.states().subscribe(this::render)) }b }d
  144. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a private val disposables = CompositeDisposable() fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } override fun onStart() { disposables.add(viewModel.states().subscribe(this::render)) viewModel.processIntents(intents()) }b }d
  145. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a private val disposables = CompositeDisposable() fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } override fun onStart() { disposables.add(viewModel.states().subscribe(this::render)) viewModel.processIntents(intents()) }b override fun onDestroy() { disposables.dispose() super.onDestroy() }c }d
  146. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a private val disposables = CompositeDisposable() fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } override fun onStart() { disposables.add(viewModel.states().subscribe(this::render)) viewModel.processIntents(intents()) }b override fun onDestroy() { disposables.dispose() super.onDestroy() }c }d
  147. class TasksFragment : Fragment() { private val viewModel: TasksViewModel by

    lazy(NONE) { ViewModelProviders.of(this).get(TasksViewModel::class.java) }a private val disposables = CompositeDisposable() fun intents(): Observable<TasksIntent> { /***/ } fun render(state: TasksViewState) { /***/ } override fun onStart() { disposables.add(viewModel.states().subscribe(this::render)) viewModel.processIntents(intents()) }b override fun onDestroy() { disposables.dispose() super.onDestroy() }c }d
  148. class TasksFragment fun intents(): Observable<TasksIntent> { return Observable.merge(initialIntent(), refreshIntent(), completeTaskIntent(),

    activateTaskIntent(), clearCompletedTaskIntent(), changeFilterIntent()) }@ }@
  149. class TasksFragment fun intents(): Observable<TasksIntent> { return Observable.merge(initialIntent(), refreshIntent(), completeTaskIntent(),

    activateTaskIntent(), clearCompletedTaskIntent(), changeFilterIntent()) }@ }@ fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent -> LoadAndFilterTasksAction /***/ }@
  150. private fun compose(): Observable<TasksViewState> { return intentsSubject .map { this.actionFromIntent(it)

    } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a
  151. private fun compose(): Observable<TasksViewState> { return intentsSubject .compose(intentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a
  152. private fun compose(): Observable<TasksViewState> { return intentsSubject .compose(intentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a private val intentFilter: ObservableTransformer<TasksIntent, TasksIntent> get() = ObservableTransformer { intents -> }z
  153. private fun compose(): Observable<TasksViewState> { return intentsSubject .compose(intentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a private val intentFilter: ObservableTransformer<TasksIntent, TasksIntent> get() = ObservableTransformer { intents -> intents.publish { shared -> Observable.merge()w }o }z
  154. private fun compose(): Observable<TasksViewState> { return intentsSubject .compose(intentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a private val intentFilter: ObservableTransformer<TasksIntent, TasksIntent> get() = ObservableTransformer { intents -> intents.publish { shared -> Observable.merge( shared.ofType(InitialIntent::class.java).take(1) )w }o }z
  155. private fun compose(): Observable<TasksViewState> { return intentsSubject .compose(intentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a private val intentFilter: ObservableTransformer<TasksIntent, TasksIntent> get() = ObservableTransformer { intents -> intents.publish { shared -> Observable.merge( shared.ofType(InitialIntent::class.java).take(1), shared.notOfType(InitialIntent::class.java) )w }o }z
  156. private fun compose(): Observable<TasksViewState> { return intentsSubject .compose(intentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) .replay(1) .autoConnect(0) }a private val intentFilter: ObservableTransformer<TasksIntent, TasksIntent> get() = ObservableTransformer { intents -> intents.publish { shared -> Observable.merge( shared.ofType(InitialIntent::class.java).take(1), shared.notOfType(InitialIntent::class.java) )w }o }z
  157. Ͷ͐஌ͬͯΔʁςετʂ

  158. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  159. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    SUBJECT PROCESSOR REPOSITORY
  160. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    TEST OBSERVER PROCESSOR REPOSITORY
  161. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  162. USER INTERFACE VIEW MODEL SUBJECT TEST OBSERVER Intent Result Action

    State INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  163. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  164. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  165. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  166. Action Load and Filter Tasks Logic Clear Completed Task Logic

    Complete Task Logic Activate Task Logic Load Tasks Logic Result PROCESSOR
  167. Load and Filter Tasks Logic Clear Completed Task Logic Complete

    Task Logic Activate Task Logic Load Tasks Logic SUBJECT TEST OBSERVER SUBJECT SUBJECT SUBJECT SUBJECT TEST OBSERVER TEST OBSERVER TEST OBSERVER TEST OBSERVER
  168. None
  169. Fin Benoît Quenaudon @oldergod