Model-View-Intent for Android

Model-View-Intent for Android

Video: https://www.youtube.com/watch?v=64rQ9GKphTg

Development on Android inevitably involves asynchronous sources, which includes the network, the framework or even the user. This easily leads to a complex architecture and a state that is difficult to manage. Are you comfortable with your users rotating the screen while waiting for a network response? What will happen when multiple requests hit the network at the same time?

The Model-View-Intent architecture, borrowed from the web, is all about streams: listening to asynchronous sources, processing the information, and displaying the result. In this architecture, everything is a stream. However, Android is a multi-threaded platform and this introduces additional complexity that we have to take into account when applying the MVI pattern. Unidirectional data flow allows one to focus on a unique source of truth and by embracing immutability, there is no need to worry about data being mutated by other parts of the code. By abstracting away the common pitfalls of state management in Android, this architecture allows the developer to focus on the logic of the app, making it easy to write and easy to maintain.

In this talk, we’ll look at how, with Kotlin and RxJava, this reactive architecture can help the developer write a complex UI with concrete examples, including parallel network requests, config changes, SnackBars and more.

05162bc961c3654218bf1839974a4f35?s=128

Benoît Quenaudon

September 26, 2017
Tweet

Transcript

  1. Model-View-Intent for Android 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. 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() }@
  42. USER INTENTS Intent Action INTENT INTERPRETOR

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

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

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

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

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

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor) )a }b }c
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

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

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

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) .map { tasks -> LoadTasksResult.Success(tasks) } }@ }@
  56. 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) } }@ }@
  57. 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()) }@ }@
  58. 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()) }@ }@
  59. 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) }@ }@
  60. 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
  61. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

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

  63. What do we need to render anything?

  64. data class TasksViewState(z )@

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

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

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

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

    tasks: List<Task>, val error: Throwable? )@
  69. 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 )@
  70. 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 )@
  71. 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) } }
  72. New State Result Default State

  73. New State REDUCER Result Default State

  74. New State REDUCER Result Previous State

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

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

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

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

    TasksResult -> when (result)_{ is LoadTasksResult -> /***/ is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. USER INTENTS Intent Result Action State INTENT INTERPRETOR REDUCER PROCESSOR

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

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

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

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

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

    null) { showLoadingTasksError() return }a }u
  90. 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
  91. 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
  92. 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
  93. 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
  94. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

    PROCESSOR REPOSITORY
  95. effects Yo Dawg, I heard you like side Yo Dawg,

    I heard you like side effects
  96. USER INTENTS RENDER Intent Result Action State INTENT INTERPRETOR REDUCER

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

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

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

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

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

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

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

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

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

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

    interface TasksViewModel { fun processIntents(intents: Observable<TasksIntent>) fun states(): Observable<TasksViewState> }@
  107. Mama Lova

  108. User: initialIntent() We: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: you.onStop()
  109. User: initialIntent() We: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: you.onStop()
  110. User: initialIntent() We: render() User: activateTask(1) User: activateTask(2) User: refresh()

    Mum: Android: you.onStop()
  111. User: initialIntent() We: render() User: activateTask(1) User: activateTask(2) User: refresh()

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

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

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

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

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

    State INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY A
  117. Config change everywhere

  118. None
  119. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

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

  121. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER

  122. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT

  123. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT

  124. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT

  125. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT SUBJECT

  126. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT SUBJECT

  127. VIEW MODEL SUBJECT SUBJECT

  128. USER INTERFACE VIEW MODEL SOURCE SUBSCRIBER SUBJECT SUBJECT

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

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

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

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

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

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

    val statesSubject: PublishSubject<TasksViewState> = PublishSubject.create() init { intentsSubject .map { intent -> actionFromIntent(intent) } .compose(actionProcessor) .scan(TasksViewState.default(), reducer) .subscribe(statesSubject) }a fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b fun states(): Observable<TasksViewState> { return statesSubject }c }e
  135. class TasksFragment : Fragment() { fun intents(): Observable<TasksIntent> { /***/

    } fun render(state: TasksViewState) { /***/ } }d
  136. 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
  137. 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
  138. 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
  139. 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
  140. 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 onStop() { disposables.dispose() super.onStop() }c }d
  141. 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 onStop() { disposables.dispose() super.onStop() }c }d
  142. 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 onStop() { disposables.dispose() super.onStop() }c }d
  143. class TasksFragment fun intents(): Observable<TasksIntent> { return Observable.merge(initialIntent(), refreshIntent(), completeTaskIntent(),

    activateTaskIntent(), clearCompletedTaskIntent(), changeFilterIntent()) }@ }@
  144. 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 /***/ }@
  145. private fun compose(): Observable<TasksViewState> { return intentsSubject .map { this.actionFromIntent(it)

    } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a
  146. private fun compose(): Observable<TasksViewState> { return intentsSubject .scan(initialIntentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a
  147. private fun compose(): Observable<TasksViewState> { return intentsSubject .scan(initialIntentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a private val initialIntentFilter = BiFunction { _: TasksIntent, newIntent: TasksIntent -> }c
  148. private fun compose(): Observable<TasksViewState> { return intentsSubject .scan(initialIntentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a private val initialIntentFilter = BiFunction { _: TasksIntent, newIntent: TasksIntent -> if (newIntent is TasksIntent.InitialIntent) { TasksIntent.GetLastState }_ }c
  149. private fun compose(): Observable<TasksViewState> { return intentsSubject .scan(initialIntentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a private val initialIntentFilter = BiFunction { _: TasksIntent, newIntent: TasksIntent -> if (newIntent is TasksIntent.InitialIntent) { TasksIntent.GetLastState }_else { newIntent }b }c
  150. private fun compose(): Observable<TasksViewState> { return intentsSubject .scan(initialIntentFilter) .map {

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a private val initialIntentFilter = BiFunction { _: TasksIntent, newIntent: TasksIntent -> if (newIntent is TasksIntent.InitialIntent) { TasksIntent.GetLastState }_else { newIntent }b }c
  151. Do you even test?

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

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

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

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

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

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

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

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

    Complete Task Logic Activate Task Logic Load Tasks Logic Result PROCESSOR
  160. 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
  161. None
  162. Fin Benoît Quenaudon @oldergod