Slide 1

Slide 1 text

MVWTF: Demystifying Architecture Patterns Adam McNeilly - @AdamMc331 @AdamMc331 ttv/adammc #DCSF24 1

Slide 2

Slide 2 text

You May Have Heard These Buzzwords: • MVC • MVP • MVVM • MVI @AdamMc331 ttv/adammc #DCSF24 2

Slide 3

Slide 3 text

Why Are There So Many? @AdamMc331 ttv/adammc #DCSF24 3

Slide 4

Slide 4 text

What's The Difference? @AdamMc331 ttv/adammc #DCSF24 4

Slide 5

Slide 5 text

Which One Should I Use? @AdamMc331 ttv/adammc #DCSF24 5

Slide 6

Slide 6 text

Which One Should I Use? @AdamMc331 ttv/adammc #DCSF24 6

Slide 7

Slide 7 text

Why Do We Need Architecture Patterns? @AdamMc331 ttv/adammc #DCSF24 7

Slide 8

Slide 8 text

More Buzzwords! @AdamMc331 ttv/adammc #DCSF24 8

Slide 9

Slide 9 text

More Buzzwords! • Maintainability @AdamMc331 ttv/adammc #DCSF24 8

Slide 10

Slide 10 text

More Buzzwords! • Maintainability • Extensibility @AdamMc331 ttv/adammc #DCSF24 8

Slide 11

Slide 11 text

More Buzzwords! • Maintainability • Extensibility • Robust @AdamMc331 ttv/adammc #DCSF24 8

Slide 12

Slide 12 text

More Buzzwords! • Maintainability • Extensibility • Robust • Testable @AdamMc331 ttv/adammc #DCSF24 8

Slide 13

Slide 13 text

Let's Start With One Simple Truth @AdamMc331 ttv/adammc #DCSF24 9

Slide 14

Slide 14 text

You Can't Put Everything In The Activity @AdamMc331 ttv/adammc #DCSF24 10

Slide 15

Slide 15 text

Or Your Fragment1 1 Thanks Mauricio for proofreading @AdamMc331 ttv/adammc #DCSF24 11

Slide 16

Slide 16 text

Why Not? @AdamMc331 ttv/adammc #DCSF24 12

Slide 17

Slide 17 text

Why Not? • Not readable @AdamMc331 ttv/adammc #DCSF24 12

Slide 18

Slide 18 text

Why Not? • Not readable • Difficult to add new code @AdamMc331 ttv/adammc #DCSF24 12

Slide 19

Slide 19 text

Why Not? • Not readable • Difficult to add new code • Difficult to change existing code @AdamMc331 ttv/adammc #DCSF24 12

Slide 20

Slide 20 text

Why Not? • Not readable • Difficult to add new code • Difficult to change existing code • Can't write Junit tests for this @AdamMc331 ttv/adammc #DCSF24 12

Slide 21

Slide 21 text

We Need To Break Up Our Code @AdamMc331 ttv/adammc #DCSF24 13

Slide 22

Slide 22 text

Let's Explore Some Options @AdamMc331 ttv/adammc #DCSF24 14

Slide 23

Slide 23 text

Model-View-Controller @AdamMc331 ttv/adammc #DCSF24 15

Slide 24

Slide 24 text

Model-View-Controller • One of the earliest architecture patterns @AdamMc331 ttv/adammc #DCSF24 15

Slide 25

Slide 25 text

Model-View-Controller • One of the earliest architecture patterns • Introduced in the 1970s as a way to organize code @AdamMc331 ttv/adammc #DCSF24 15

Slide 26

Slide 26 text

Model-View-Controller • One of the earliest architecture patterns • Introduced in the 1970s as a way to organize code • Divides application to three parts @AdamMc331 ttv/adammc #DCSF24 15

Slide 27

Slide 27 text

Model @AdamMc331 ttv/adammc #DCSF24 16

Slide 28

Slide 28 text

Model • This is your data source @AdamMc331 ttv/adammc #DCSF24 16

Slide 29

Slide 29 text

Model • This is your data source • Database, remote server, etc @AdamMc331 ttv/adammc #DCSF24 16

Slide 30

Slide 30 text

Model • This is your data source • Database, remote server, etc • It does not care about the view @AdamMc331 ttv/adammc #DCSF24 16

Slide 31

Slide 31 text

View @AdamMc331 ttv/adammc #DCSF24 17

Slide 32

Slide 32 text

View • This is the visual representation of information @AdamMc331 ttv/adammc #DCSF24 17

Slide 33

Slide 33 text

View • This is the visual representation of information • Does not care where this data came from @AdamMc331 ttv/adammc #DCSF24 17

Slide 34

Slide 34 text

View • This is the visual representation of information • Does not care where this data came from • Only responsible for displaying data @AdamMc331 ttv/adammc #DCSF24 17

Slide 35

Slide 35 text

View • This is the visual representation of information • Does not care where this data came from • Only responsible for displaying data • If your view has a conditional, consider refactoring @AdamMc331 ttv/adammc #DCSF24 17

Slide 36

Slide 36 text

Controller @AdamMc331 ttv/adammc #DCSF24 18

Slide 37

Slide 37 text

Controller • Handles user inputs @AdamMc331 ttv/adammc #DCSF24 18

Slide 38

Slide 38 text

Controller • Handles user inputs • Validates if necessary @AdamMc331 ttv/adammc #DCSF24 18

Slide 39

Slide 39 text

Controller • Handles user inputs • Validates if necessary • Passes input to model @AdamMc331 ttv/adammc #DCSF24 18

Slide 40

Slide 40 text

Controller • Handles user inputs • Validates if necessary • Passes input to model • Passes model response to view @AdamMc331 ttv/adammc #DCSF24 18

Slide 41

Slide 41 text

The Model & View Components Are The Same For All Patterns @AdamMc331 ttv/adammc #DCSF24 19

Slide 42

Slide 42 text

@AdamMc331 ttv/adammc #DCSF24 20

Slide 43

Slide 43 text

Model-View-WhateverTheFYouWant @AdamMc331 ttv/adammc #DCSF24 21

Slide 44

Slide 44 text

Why Do We Have So Many Options For This Third Component? @AdamMc331 ttv/adammc #DCSF24 22

Slide 45

Slide 45 text

Short Answer: State Management @AdamMc331 ttv/adammc #DCSF24 23

Slide 46

Slide 46 text

Long Answer: Let's Break Them Down @AdamMc331 ttv/adammc #DCSF24 24

Slide 47

Slide 47 text

Model-View-Controller @AdamMc331 ttv/adammc #DCSF24 25

Slide 48

Slide 48 text

Why Don't We Use This For Android? @AdamMc331 ttv/adammc #DCSF24 26

Slide 49

Slide 49 text

Why Don't We Use This For Android? @AdamMc331 ttv/adammc #DCSF24 27

Slide 50

Slide 50 text

Why Don't We Use This For Android? @AdamMc331 ttv/adammc #DCSF24 28

Slide 51

Slide 51 text

Why Don't We Use This For Android? • We can't write Junit tests for an Activity @AdamMc331 ttv/adammc #DCSF24 28

Slide 52

Slide 52 text

Why Don't We Use This For Android? • We can't write Junit tests for an Activity • We can't unit test our UI logic @AdamMc331 ttv/adammc #DCSF24 28

Slide 53

Slide 53 text

Why Don't We Use This For Android? • We can't write Junit tests for an Activity • We can't unit test our UI logic • We don't really have a separation of concerns here @AdamMc331 ttv/adammc #DCSF24 28

Slide 54

Slide 54 text

Model-View-Presenter @AdamMc331 ttv/adammc #DCSF24 29

Slide 55

Slide 55 text

Model-View-Presenter @AdamMc331 ttv/adammc #DCSF24 30

Slide 56

Slide 56 text

Model-View-Presenter • Similar to the last pattern @AdamMc331 ttv/adammc #DCSF24 30

Slide 57

Slide 57 text

Model-View-Presenter • Similar to the last pattern • Moves our presentation logic out of the Activity class @AdamMc331 ttv/adammc #DCSF24 30

Slide 58

Slide 58 text

Model-View-Presenter @AdamMc331 ttv/adammc #DCSF24 31

Slide 59

Slide 59 text

Why Is This Better? @AdamMc331 ttv/adammc #DCSF24 32

Slide 60

Slide 60 text

Why Is This Better? • UI logic is outside of the Activity, and now supports Junit tests @AdamMc331 ttv/adammc #DCSF24 32

Slide 61

Slide 61 text

Why Is This Better? • UI logic is outside of the Activity, and now supports Junit tests • Our concerns are separated again @AdamMc331 ttv/adammc #DCSF24 32

Slide 62

Slide 62 text

MVP Implementation @AdamMc331 ttv/adammc #DCSF24 33

Slide 63

Slide 63 text

Contract Class object TaskListContract { interface Model { // ... } interface View { // ... } interface Presenter { // ... } } @AdamMc331 ttv/adammc #DCSF24 34

Slide 64

Slide 64 text

Contract Class object TaskListContract { interface Model { suspend fun getTasks(): List } // ... } @AdamMc331 ttv/adammc #DCSF24 35

Slide 65

Slide 65 text

Contract Class object TaskListContract { interface View { fun render(tasks: List) } // ... } @AdamMc331 ttv/adammc #DCSF24 36

Slide 66

Slide 66 text

Contract Class object TaskListContract { interface Presenter { fun viewCreated() fun viewDestroyed() } // ... } @AdamMc331 ttv/adammc #DCSF24 37

Slide 67

Slide 67 text

Model class InMemoryTaskRepository : TaskListContract.Model { override suspend fun getTasks(): List { return listOf( Task("Test Task 1"), Task("Test Task 2"), Task("Test Task 3"), ) } } @AdamMc331 ttv/adammc #DCSF24 38

Slide 68

Slide 68 text

View class MainActivity : TaskListContract.View { private val presenter = TaskListPresenter( view = this, model = InMemoryTaskRepository(), ) // ... } @AdamMc331 ttv/adammc #DCSF24 39

Slide 69

Slide 69 text

View class MainActivity { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) presenter.viewCreated() } // ... } @AdamMc331 ttv/adammc #DCSF24 40

Slide 70

Slide 70 text

View class MainActivity { override fun render(tasks: List) { setContent { TaskList(tasks) } } // ... } @AdamMc331 ttv/adammc #DCSF24 41

Slide 71

Slide 71 text

Presenter class TaskListPresenter( private var view: TaskListContract.View?, private val model: TaskListContract.Model, ) : TaskListContract.Presenter { // ... } @AdamMc331 ttv/adammc #DCSF24 42

Slide 72

Slide 72 text

Presenter class TaskListPresenter { private var tasks: List = emptyList() set(value) { field = value view?.render(value) } // ... } @AdamMc331 ttv/adammc #DCSF24 43

Slide 73

Slide 73 text

Presenter class TaskListPresenter { override fun viewCreated() { presenterScope.launch { tasks = model.getTasks() } } // ... } @AdamMc331 ttv/adammc #DCSF24 44

Slide 74

Slide 74 text

Presenter class TaskListPresenter { override fun viewDestroyed() { view = null } // ... } @AdamMc331 ttv/adammc #DCSF24 45

Slide 75

Slide 75 text

State Restoration @AdamMc331 ttv/adammc #DCSF24 46

Slide 76

Slide 76 text

State Restoration object TaskListContract { interface Presenter { // New: fun getTasks(): List fun restoreTasks(tasks: List) } } @AdamMc331 ttv/adammc #DCSF24 47

Slide 77

Slide 77 text

Persist State class MainActivity { override fun onSaveInstanceState(outState: Bundle) { outState.putParcelableArrayList("tasks", presenter.getTasks()) super.onSaveInstanceState(outState) } // ... } @AdamMc331 ttv/adammc #DCSF24 48

Slide 78

Slide 78 text

Restore State class MainActivity { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val tasks = savedInstanceState?.getParcelableArrayList("tasks") if (tasks != null) { presenter.restoreTasks(tasks) } } // ... } @AdamMc331 ttv/adammc #DCSF24 49

Slide 79

Slide 79 text

MVP Recap @AdamMc331 ttv/adammc #DCSF24 50

Slide 80

Slide 80 text

MVP Recap • View does nothing but display data @AdamMc331 ttv/adammc #DCSF24 50

Slide 81

Slide 81 text

MVP Recap • View does nothing but display data • Data fetching is all handled by model @AdamMc331 ttv/adammc #DCSF24 50

Slide 82

Slide 82 text

MVP Recap • View does nothing but display data • Data fetching is all handled by model • Presentation of data is handled by presenter @AdamMc331 ttv/adammc #DCSF24 50

Slide 83

Slide 83 text

MVP Recap • View does nothing but display data • Data fetching is all handled by model • Presentation of data is handled by presenter • Everything is separated, everything is testable @AdamMc331 ttv/adammc #DCSF24 50

Slide 84

Slide 84 text

MVP Recap • View does nothing but display data • Data fetching is all handled by model • Presentation of data is handled by presenter • Everything is separated, everything is testable • State can be fetched/restored as necessary @AdamMc331 ttv/adammc #DCSF24 50

Slide 85

Slide 85 text

What's Different About MVVM? @AdamMc331 ttv/adammc #DCSF24 51

Slide 86

Slide 86 text

The Presenter Doesn't Need To Care About The View @AdamMc331 ttv/adammc #DCSF24 52

Slide 87

Slide 87 text

Model-View-ViewModel @AdamMc331 ttv/adammc #DCSF24 53

Slide 88

Slide 88 text

MVVM Implementation @AdamMc331 ttv/adammc #DCSF24 54

Slide 89

Slide 89 text

Model Doesn't Change (much) interface TaskRepository { fun getTasks(): List } class InMemoryTaskService : TaskRepository { override fun getTasks(): List { return listOf(...) } } @AdamMc331 ttv/adammc #DCSF24 55

Slide 90

Slide 90 text

ViewModel class TaskListViewModel( taskRepository: TaskRepository, ) { private val mutableTasks = MutableStateFlow(emptyList()) val tasks = mutableTasks.asStateFlow() // ... } @AdamMc331 ttv/adammc #DCSF24 56

Slide 91

Slide 91 text

ViewModel class TaskListViewModel { init { viewModelScope.launch { tasks.value = taskRepository.getTasks() } } // ... } @AdamMc331 ttv/adammc #DCSF24 57

Slide 92

Slide 92 text

View class MainActivity { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val tasks by viewModel.tasks.collectAsState() TaskList(tasks) } } // ... } @AdamMc331 ttv/adammc #DCSF24 58

Slide 93

Slide 93 text

This Is Pretty Close To MVP, With One New Benefit @AdamMc331 ttv/adammc #DCSF24 59

Slide 94

Slide 94 text

Since ViewModel Doesn't Reference View, We Can Leverage Android Architecture Component ViewModel To Outlast Config Changes @AdamMc331 ttv/adammc #DCSF24 60

Slide 95

Slide 95 text

State Restoration In MVVM @AdamMc331 ttv/adammc #DCSF24 61

Slide 96

Slide 96 text

State Restoration In MVVM 1. Have ViewModel class extend the AndroidX ViewModel class @AdamMc331 ttv/adammc #DCSF24 61

Slide 97

Slide 97 text

State Restoration In MVVM 1. Have ViewModel class extend the AndroidX ViewModel class 2. Update Activity to use ViewModelProviders @AdamMc331 ttv/adammc #DCSF24 61

Slide 98

Slide 98 text

State Restoration In MVVM 1. Have ViewModel class extend the AndroidX ViewModel class 2. Update Activity to use ViewModelProviders 3. Since Android's ViewModel outlasts config changes, no need to save/restore state, just re-subscribe @AdamMc331 ttv/adammc #DCSF24 61

Slide 99

Slide 99 text

State Restoration In MVVM class TaskListViewModel( private val repository: TaskRepository ) : ViewModel() { // ... } @AdamMc331 ttv/adammc #DCSF24 62

Slide 100

Slide 100 text

State Restoration In MVVM class MainActivity { private val viewModel: TaskListViewModel by viewModels { // ... } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val tasks by viewModel.tasks.collectAsState() TaskList(tasks) } } } @AdamMc331 ttv/adammc #DCSF24 63

Slide 101

Slide 101 text

MVVM Recap @AdamMc331 ttv/adammc #DCSF24 64

Slide 102

Slide 102 text

MVVM Recap • All the benefits of MVP @AdamMc331 ttv/adammc #DCSF24 64

Slide 103

Slide 103 text

MVVM Recap • All the benefits of MVP • Decoupled view and presentation layer @AdamMc331 ttv/adammc #DCSF24 64

Slide 104

Slide 104 text

MVVM Recap • All the benefits of MVP • Decoupled view and presentation layer • Easier support for configuration changes @AdamMc331 ttv/adammc #DCSF24 64

Slide 105

Slide 105 text

Where Does MVVM Fall Short? @AdamMc331 ttv/adammc #DCSF24 65

Slide 106

Slide 106 text

Let's Consider A More Complicated State @AdamMc331 ttv/adammc #DCSF24 66

Slide 107

Slide 107 text

Let's Consider A More Complicated State sealed interface TaskListState { object Loading : TaskListState data class Loaded(val tasks: List) : TaskListState data class Error(val message: String) : TaskListState } @AdamMc331 ttv/adammc #DCSF24 67

Slide 108

Slide 108 text

Let's Consider A More Complicated State class TaskListViewModel(private val repository: TaskRepository) : ViewModel() { // ... private fun showLoading() { state.value = TaskListState.Loading } private fun fetchTasks() { val tasks = repository.getItems() state.value = TaskListState.Loaded(tasks) } private fun showError() { state.value = TaskListState.Error("Unable to fetch tasks.") } } @AdamMc331 ttv/adammc #DCSF24 68

Slide 109

Slide 109 text

What Are The Risks Of These Methods? private fun showLoading() { state.value = TaskListState.Loading } private fun fetchTasks() { val tasks = repository.getItems() state.value = TaskListState.Loaded(tasks) } private fun showError() { state.value = TaskListState.Error("Unable to fetch tasks.") } @AdamMc331 ttv/adammc #DCSF24 69

Slide 110

Slide 110 text

What Are The Risks Of These Methods? @AdamMc331 ttv/adammc #DCSF24 70

Slide 111

Slide 111 text

What Are The Risks Of These Methods? • Any methods in the class can call them @AdamMc331 ttv/adammc #DCSF24 70

Slide 112

Slide 112 text

What Are The Risks Of These Methods? • Any methods in the class can call them • We can't guarantee they're associated with a specific action or intent @AdamMc331 ttv/adammc #DCSF24 70

Slide 113

Slide 113 text

What Are The Risks Of These Methods? • Any methods in the class can call them • We can't guarantee they're associated with a specific action or intent • We have multiple methods manipulating our state that we have to ensure don't conflict with each other @AdamMc331 ttv/adammc #DCSF24 70

Slide 114

Slide 114 text

How Can We Mitigate This Risk? @AdamMc331 ttv/adammc #DCSF24 71

Slide 115

Slide 115 text

How Can We Mitigate This Risk? • Have one single source of truth for our state @AdamMc331 ttv/adammc #DCSF24 71

Slide 116

Slide 116 text

How Can We Mitigate This Risk? • Have one single source of truth for our state • Do this through a single pipeline where every action causes a specific change in the state @AdamMc331 ttv/adammc #DCSF24 71

Slide 117

Slide 117 text

How Can We Mitigate This Risk? • Have one single source of truth for our state • Do this through a single pipeline where every action causes a specific change in the state • This makes state changes predictable, and therefore highly testable as well @AdamMc331 ttv/adammc #DCSF24 71

Slide 118

Slide 118 text

Model-View-Intent @AdamMc331 ttv/adammc #DCSF24 72

Slide 119

Slide 119 text

Model-View-Intent @AdamMc331 ttv/adammc #DCSF24 73

Slide 120

Slide 120 text

Model-View-Intent • Unlike the previous patterns, "Intent" isn't used to reference a specific kind of component, but rather the intention of doing something that we want to capture in our state. @AdamMc331 ttv/adammc #DCSF24 73

Slide 121

Slide 121 text

The First Goal Is To Make Our State Changes Predictable @AdamMc331 ttv/adammc #DCSF24 74

Slide 122

Slide 122 text

We Can Achieve This With A State Machine class StateMachine( initialState: State, private val eventProcessor: (State, StateUpdateEvent) -> State, ) { private val mutableState = MutableStateFlow(initialState) val state = mutableState.asStateFlow() fun processEvent(event: StateUpdateEvent) { mutableState.update { currentState -> val newState = eventProcessor(currentState, event) newState } } } @AdamMc331 ttv/adammc #DCSF24 75

Slide 123

Slide 123 text

We Can Achieve This With A State Machine class StateMachine( initialState: State, private val eventProcessor: (State, StateUpdateEvent) -> State, ) { private val mutableState = MutableStateFlow(initialState) val state = mutableState.asStateFlow() fun processEvent(event: StateUpdateEvent) { mutableState.update { currentState -> val newState = eventProcessor(currentState, event) newState } } } @AdamMc331 ttv/adammc #DCSF24 75

Slide 124

Slide 124 text

We Can Achieve This With A State Machine class StateMachine( initialState: State, private val eventProcessor: (State, StateUpdateEvent) -> State, ) { private val mutableState = MutableStateFlow(initialState) val state = mutableState.asStateFlow() fun processEvent(event: StateUpdateEvent) { mutableState.update { currentState -> val newState = eventProcessor(currentState, event) newState } } } @AdamMc331 ttv/adammc #DCSF24 75

Slide 125

Slide 125 text

Clearly Defined Inputs sealed class TaskListStateUpdateEvent : StateUpdateEvent { data object SetLoading : TaskListStateUpdateEvent() data class SetTasks(val tasks: List) : TaskListStateUpdateEvent() data class SetError(val error: String) : TaskListStateUpdateEvent() } @AdamMc331 ttv/adammc #DCSF24 76

Slide 126

Slide 126 text

Clearly Defined Outputs private val stateMachine = StateMachine( initialState = TaskListViewState.Loading, eventProcessor = { currentState, event -> when (event) { is TaskListStateUpdateEvent.SetError -> { TaskListViewState.Error(event.error) } TaskListStateUpdateEvent.SetLoading -> { TaskListViewState.Loading } is TaskListStateUpdateEvent.SetTasks -> { TaskListViewState.Loaded(event.tasks) } } }, ) @AdamMc331 ttv/adammc #DCSF24 77

Slide 127

Slide 127 text

Clearly Defined Outputs private val stateMachine = StateMachine( initialState = TaskListViewState.Loading, eventProcessor = { currentState, event -> when (event) { is TaskListStateUpdateEvent.SetError -> { TaskListViewState.Error(event.error) } TaskListStateUpdateEvent.SetLoading -> { TaskListViewState.Loading } is TaskListStateUpdateEvent.SetTasks -> { TaskListViewState.Loaded(event.tasks) } } }, ) @AdamMc331 ttv/adammc #DCSF24 77

Slide 128

Slide 128 text

This State Machine Is Our Source Of Truth class TaskListViewModel { private val stateMachine = // ... val state = stateMachine.state } @AdamMc331 ttv/adammc #DCSF24 78

Slide 129

Slide 129 text

Side Effects @AdamMc331 ttv/adammc #DCSF24 79

Slide 130

Slide 130 text

Side Effects class StateMachine( // ... private val eventProcessor: (State, StateUpdateEvent) -> StateAndSideEffects, private val sideEffectProcessor: (SideEffect) -> Unit, ) data class StateAndSideEffects( val state: State, val sideEffects: List, ) @AdamMc331 ttv/adammc #DCSF24 80

Slide 131

Slide 131 text

Task List Flow In MVI @AdamMc331 ttv/adammc #DCSF24 81

Slide 132

Slide 132 text

ViewModel Initialization class TaskListViewModel { init { stateMachine.processEvent(TaskListStateUpdateEvent.SetLoading) } } @AdamMc331 ttv/adammc #DCSF24 82

Slide 133

Slide 133 text

Process Loading Event private val stateMachine = StateMachine( eventProcessor = { currentState, event -> when (event) { TaskListStateUpdateEvent.SetLoading -> { TaskListViewState.Loading + TaskListSideEffect.FetchTasks } // ... } }, ) @AdamMc331 ttv/adammc #DCSF24 83

Slide 134

Slide 134 text

Process Side Effect private val stateMachine = StateMachine( // ... sideEffectProcessor = { sideEffect -> when (sideEffect) { TaskListSideEffect.FetchTasks -> { fetchTasks() } } }, ) @AdamMc331 ttv/adammc #DCSF24 84

Slide 135

Slide 135 text

Process Side Effect private fun fetchTasks() { viewModelScope.launch { val event = try { val tasks = taskRepository.getTasks() TaskListStateUpdateEvent.SetTasks(tasks) } catch (e: Exception) { TaskListStateUpdateEvent.SetError(e.message) } stateMachine.processEvent(event) } } @AdamMc331 ttv/adammc #DCSF24 85

Slide 136

Slide 136 text

Process Resulting Events private val stateMachine = StateMachine( eventProcessor = { currentState, event -> when (event) { is TaskListStateUpdateEvent.SetError -> { TaskListViewState.Error(event.error).noSideEffects() } is TaskListStateUpdateEvent.SetTasks -> { TaskListViewState.Loaded(event.tasks).noSideEffects() } // ... } } ) @AdamMc331 ttv/adammc #DCSF24 86

Slide 137

Slide 137 text

MVI Recap @AdamMc331 ttv/adammc #DCSF24 87

Slide 138

Slide 138 text

MVI Recap • All benefits of previous patterns @AdamMc331 ttv/adammc #DCSF24 87

Slide 139

Slide 139 text

MVI Recap • All benefits of previous patterns • State management is clear and predictable @AdamMc331 ttv/adammc #DCSF24 87

Slide 140

Slide 140 text

Is MVI The Best We Can Do? @AdamMc331 ttv/adammc #DCSF24 88

Slide 141

Slide 141 text

Is MVI The Best We Can Do? • State management is pretty solid @AdamMc331 ttv/adammc #DCSF24 88

Slide 142

Slide 142 text

Is MVI The Best We Can Do? • State management is pretty solid • But, we have 22 letters that weren't covered yet @AdamMc331 ttv/adammc #DCSF24 88

Slide 143

Slide 143 text

What Should I Take Away From This? @AdamMc331 ttv/adammc #DCSF24 89

Slide 144

Slide 144 text

Model-View-Presenter @AdamMc331 ttv/adammc #DCSF24 90

Slide 145

Slide 145 text

Model-View-Presenter • Seperated concerns and testing support @AdamMc331 ttv/adammc #DCSF24 90

Slide 146

Slide 146 text

Model-View-Presenter • Seperated concerns and testing support • Okay for quick prototyping @AdamMc331 ttv/adammc #DCSF24 90

Slide 147

Slide 147 text

Model-View-Presenter • Seperated concerns and testing support • Okay for quick prototyping • Managing state across config changes requires work @AdamMc331 ttv/adammc #DCSF24 90

Slide 148

Slide 148 text

Model-View-Presenter • Seperated concerns and testing support • Okay for quick prototyping • Managing state across config changes requires work • State management is unpredictable @AdamMc331 ttv/adammc #DCSF24 90

Slide 149

Slide 149 text

Model-View-ViewModel @AdamMc331 ttv/adammc #DCSF24 91

Slide 150

Slide 150 text

Model-View-ViewModel • Seperated concerns and testing support @AdamMc331 ttv/adammc #DCSF24 91

Slide 151

Slide 151 text

Model-View-ViewModel • Seperated concerns and testing support • Even better for quick prototyping @AdamMc331 ttv/adammc #DCSF24 91

Slide 152

Slide 152 text

Model-View-ViewModel • Seperated concerns and testing support • Even better for quick prototyping • Can handle config changes easily if we use Android's architecture components @AdamMc331 ttv/adammc #DCSF24 91

Slide 153

Slide 153 text

Model-View-ViewModel • Seperated concerns and testing support • Even better for quick prototyping • Can handle config changes easily if we use Android's architecture components • State management is unpredictable @AdamMc331 ttv/adammc #DCSF24 91

Slide 154

Slide 154 text

Model-View-Intent @AdamMc331 ttv/adammc #DCSF24 92

Slide 155

Slide 155 text

Model-View-Intent • Seperated concerns and testing support @AdamMc331 ttv/adammc #DCSF24 92

Slide 156

Slide 156 text

Model-View-Intent • Seperated concerns and testing support • Works with both a Presenter and a ViewModel @AdamMc331 ttv/adammc #DCSF24 92

Slide 157

Slide 157 text

Model-View-Intent • Seperated concerns and testing support • Works with both a Presenter and a ViewModel • Not good for quick prototyping @AdamMc331 ttv/adammc #DCSF24 92

Slide 158

Slide 158 text

Model-View-Intent • Seperated concerns and testing support • Works with both a Presenter and a ViewModel • Not good for quick prototyping • State management is clear and predictable @AdamMc331 ttv/adammc #DCSF24 92

Slide 159

Slide 159 text

Model-View-Intent • Seperated concerns and testing support • Works with both a Presenter and a ViewModel • Not good for quick prototyping • State management is clear and predictable • Has a steeper learning curve due to state machine logic @AdamMc331 ttv/adammc #DCSF24 92

Slide 160

Slide 160 text

General Suggestions @AdamMc331 ttv/adammc #DCSF24 93

Slide 161

Slide 161 text

General Suggestions • MVP can get you up and running quickly, but due to the boilerplate and config changes work I wouldn't recommend it @AdamMc331 ttv/adammc #DCSF24 93

Slide 162

Slide 162 text

General Suggestions • MVP can get you up and running quickly, but due to the boilerplate and config changes work I wouldn't recommend it • MVVM is what I'd recommend the most. It allows for separation of concerns and unit test support without a major learning curve @AdamMc331 ttv/adammc #DCSF24 93

Slide 163

Slide 163 text

General Suggestions • MVP can get you up and running quickly, but due to the boilerplate and config changes work I wouldn't recommend it • MVVM is what I'd recommend the most. It allows for separation of concerns and unit test support without a major learning curve • If your app handles complex user flows or states, MVI can give you more support for state management @AdamMc331 ttv/adammc #DCSF24 93

Slide 164

Slide 164 text

What's Most Important @AdamMc331 ttv/adammc #DCSF24 94

Slide 165

Slide 165 text

What's Most Important • Be consistent @AdamMc331 ttv/adammc #DCSF24 94

Slide 166

Slide 166 text

What's Most Important • Be consistent • Be comfortable @AdamMc331 ttv/adammc #DCSF24 94

Slide 167

Slide 167 text

Thank you! https://github.com/adammc331/mvwtf2024 @AdamMc331 ttv/adammc #DCSF24 95