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

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.

Benoît Quenaudon

September 26, 2017
Tweet

More Decks by Benoît Quenaudon

Other Decks in Programming

Transcript

  1. sealed class TasksIntent { object InitialIntent : TasksIntent() object RefreshIntent

    : TasksIntent() data class ActivateTaskIntent(val task: Task) : TasksIntent() data class CompleteTaskIntent(val task: Task) : TasksIntent() }@
  2. 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() }@
  3. 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() }@
  4. 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() }@
  5. class TasksFragment fun intents(): Observable<TasksIntent> { return initialIntent() }@a private

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

    refreshIntent(): Observable<RefreshIntent> { return RxSwipeRefreshLayout.refreshes(swipeRefreshLayout) .map { RefreshIntent } }@ }@
  7. fun actionFromIntent(intent: TasksIntent): TasksAction = when (intent) { is InitialIntent

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

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

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

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

    -> LoadAndFilterTasksAction(TasksFilterType.ALL_TASKS) is RefreshIntent -> LoadTasksAction is ActivateTaskIntent -> is CompleteTaskIntent -> is ClearCompletedTasksIntent -> is ChangeFilterIntent -> }@
  12. 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 -> }@
  13. 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 -> }@
  14. 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 -> }@
  15. 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) }@
  16. 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) }@
  17. 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() }@
  18. Action Load and Filter Tasks Logic Clear Completed Task Logic

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

    actions.publish { shared -> Observable.merge( shared.ofType(LoadTasksAction::class.java).compose(loadTasksProcessor) )a }b }c
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. val loadTasksProcessor = ObservableTransformer { actions: Observable<LoadTasksAction> -> actions.switchMap {

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

    tasksRepository.getTasks() // Observable<List<Tasks>> .startWith(LoadTasksResult.InFlight) .map { tasks -> LoadTasksResult.Success(tasks) } }@ }@
  27. 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) } }@ }@
  28. 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()) }@ }@
  29. 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()) }@ }@
  30. 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) }@ }@
  31. 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
  32. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

    Observable<TasksAction> .compose(actionProcessor) // Observable<TasksResult>
  33. 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 )@
  34. 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 )@
  35. 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) } }
  36. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

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

    TasksResult -> when (result)_{ is LoadTasksResult -> /***/ is LoadAndFilterTasksResult -> /***/ is CompleteTaskResult -> /***/ is ActivateTaskResult -> /***/ is ClearCompletedTasksResult -> /***/ }d }e
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. intents // Observable<TasksIntent> .map { intent -> actionFromIntent(intent) } //

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

    Observable<TasksAction> .compose(actionProcessor) // Observable<TasksResult> .scan(TasksViewState.default(), reducer) // Observable<TasksViewState> .subscribe { state -> render(state) }
  45. 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
  46. 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
  47. 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
  48. 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
  49. effects Yo Dawg, I heard you like side Yo Dawg,

    I heard you like side effects
  50. USER USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action

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

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

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

    interface TasksViewModel { fun processIntents(intents: Observable<TasksIntent>) fun states(): Observable<TasksViewState> }@
  54. USER USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action

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

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

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

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

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

    INTENT INTERPRETOR REDUCER PROCESSOR REPOSITORY
  60. class TasksViewModel : ViewModel() { val intentsSubject: PublishSubject<TasksIntent> = PublishSubject.create()

    fun processIntents(intents: Observable<TasksIntent>) { intents.subscribe(intentsSubject) }b fun states(): Observable<TasksViewState> { }c }e
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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 /***/ }@
  73. private fun compose(): Observable<TasksViewState> { return intentsSubject .map { this.actionFromIntent(it)

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

    this.actionFromIntent(it) } .compose(actionProcessorHolder.actionProcessor) .scan(TasksViewState.idle(), reducer) }a
  75. 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
  76. 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
  77. 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
  78. 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
  79. USER INTERFACE VIEW MODEL INTENTS RENDER Intent Result Action State

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

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

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

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

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

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

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

    Complete Task Logic Activate Task Logic Load Tasks Logic Result PROCESSOR
  87. 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