Slide 1

Slide 1 text

MVWTF: Demystifying Architecture Patterns Adam McNeilly - @AdamMc331 @AdamMc331 #AndroidSummit 1

Slide 2

Slide 2 text

You May Have Heard These Buzzwords: • MVC • MVP • MVVM • MVI • MVU?? @AdamMc331 #AndroidSummit 2

Slide 3

Slide 3 text

Why Are There So Many? @AdamMc331 #AndroidSummit 3

Slide 4

Slide 4 text

What's The Difference? @AdamMc331 #AndroidSummit 4

Slide 5

Slide 5 text

Which One Should I Use? @AdamMc331 #AndroidSummit 5

Slide 6

Slide 6 text

Which One Should I Use? @AdamMc331 #AndroidSummit 6

Slide 7

Slide 7 text

Why Do We Need Architecture Patterns? @AdamMc331 #AndroidSummit 7

Slide 8

Slide 8 text

More Buzzwords! • Maintainability • Extensibility • Robust • Testable @AdamMc331 #AndroidSummit 8

Slide 9

Slide 9 text

Let's Start With One Simple Truth @AdamMc331 #AndroidSummit 9

Slide 10

Slide 10 text

You Can't Put Everything In The Activity @AdamMc331 #AndroidSummit 10

Slide 11

Slide 11 text

Or Your Fragment1 1 Thanks Mauricio for proofreading @AdamMc331 #AndroidSummit 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

We Need To Break Up Our Code @AdamMc331 #AndroidSummit 13

Slide 14

Slide 14 text

Let's Explore Some Options @AdamMc331 #AndroidSummit 14

Slide 15

Slide 15 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 #AndroidSummit 15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 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 #AndroidSummit 17

Slide 18

Slide 18 text

Controller • Handles user inputs • Validates if necessary • Passes input to model • Passes model response to view @AdamMc331 #AndroidSummit 18

Slide 19

Slide 19 text

The Model & View Components Are The Same For All Patterns @AdamMc331 #AndroidSummit 19

Slide 20

Slide 20 text

@AdamMc331 #AndroidSummit 20

Slide 21

Slide 21 text

Model-View-WhateverTheFYouWant @AdamMc331 #AndroidSummit 21

Slide 22

Slide 22 text

Why Do We Have So Many Options For This Third Component? @AdamMc331 #AndroidSummit 22

Slide 23

Slide 23 text

Short Answer: State Management @AdamMc331 #AndroidSummit 23

Slide 24

Slide 24 text

Long Answer: Let's Break Them Down @AdamMc331 #AndroidSummit 24

Slide 25

Slide 25 text

Model-View-Controller @AdamMc331 #AndroidSummit 25

Slide 26

Slide 26 text

Why Don't We Use This For Android? @AdamMc331 #AndroidSummit 26

Slide 27

Slide 27 text

Why Don't We Use This For Android? @AdamMc331 #AndroidSummit 27

Slide 28

Slide 28 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 #AndroidSummit 28

Slide 29

Slide 29 text

Model-View-Presenter @AdamMc331 #AndroidSummit 29

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Model-View-Presenter @AdamMc331 #AndroidSummit 31

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

MVP Implementation @AdamMc331 #AndroidSummit 33

Slide 34

Slide 34 text

Contract Class class TaskListContract { interface View { fun showTasks(tasks: List) } interface Presenter { fun viewCreated() fun viewDestroyed() } interface Model { fun getTasks(): List } } @AdamMc331 #AndroidSummit 34

Slide 35

Slide 35 text

Contract Class class TaskListContract { interface View { fun showTasks(tasks: List) } interface Presenter { fun viewCreated() fun viewDestroyed() } interface Model { fun getTasks(): List } } @AdamMc331 #AndroidSummit 35

Slide 36

Slide 36 text

Model class InMemoryTaskService : TaskListContract.Model { override fun getTasks(): List { return listOf( Task("Sample task 1"), Task("Sample task 2") ) } } @AdamMc331 #AndroidSummit 36

Slide 37

Slide 37 text

View class TaskListActivity : AppCompatActivity(), TaskListContract.View { private val taskAdapter = TaskAdapter() private val presenter = TaskListPresenter(this, TaskRepository()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... presenter.viewCreated() } override fun onDestroy() { presenter.viewDestroyed() super.onDestroy() } override fun showTasks(tasks: List) { taskAdapter.tasks = tasks } } @AdamMc331 #AndroidSummit 37

Slide 38

Slide 38 text

View class TaskListActivity : AppCompatActivity(), TaskListContract.View { private val taskAdapter = TaskAdapter() private val presenter = TaskListPresenter(this, TaskRepository()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... presenter.viewCreated() } override fun onDestroy() { presenter.viewDestroyed() super.onDestroy() } override fun showTasks(tasks: List) { taskAdapter.tasks = tasks } } @AdamMc331 #AndroidSummit 38

Slide 39

Slide 39 text

Presenter class TaskListPresenter( private var view: TaskListContract.View?, private val model: TaskListContract.Model ) : TaskListContract.Presenter { override fun viewCreated() { val tasks = model.getTasks() view?.showTasks(tasks) } override fun viewDestroyed() { view = null } } @AdamMc331 #AndroidSummit 39

Slide 40

Slide 40 text

Presenter class TaskListPresenter( private var view: TaskListContract.View?, private val model: TaskListContract.Model ) : TaskListContract.Presenter { override fun viewCreated() { val tasks = model.getTasks() view?.showTasks(tasks) } override fun viewDestroyed() { view = null } } @AdamMc331 #AndroidSummit 40

Slide 41

Slide 41 text

Is That Enough? • 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 • If you think this is good enough, use it! @AdamMc331 #AndroidSummit 41

Slide 42

Slide 42 text

What's Different About MVVM? @AdamMc331 #AndroidSummit 42

Slide 43

Slide 43 text

The Presenter Doesn't Need To Care About The View @AdamMc331 #AndroidSummit 43

Slide 44

Slide 44 text

Model-View-ViewModel @AdamMc331 #AndroidSummit 44

Slide 45

Slide 45 text

MVVM Implementation @AdamMc331 #AndroidSummit 45

Slide 46

Slide 46 text

Model Doesn't Change (much) interface TaskRepository { fun getTasks(): List } class InMemoryTaskService : TaskRepository { override fun getTasks(): List { return listOf(...) } } @AdamMc331 #AndroidSummit 46

Slide 47

Slide 47 text

ViewModel class TaskListViewModel( private val repository: TaskRepository ) { private val tasks = MutableLiveData>() fun getTasks(): LiveData> = tasks init { fetchTasks() } private fun fetchTasks() { tasks.value = repository.getTasks() } } @AdamMc331 #AndroidSummit 47

Slide 48

Slide 48 text

ViewModel class TaskListViewModel( private val repository: TaskRepository ) { private val tasks = MutableLiveData>() fun getTasks(): LiveData> = tasks init { fetchTasks() } private fun fetchTasks() { tasks.value = repository.getTasks() } } @AdamMc331 #AndroidSummit 48

Slide 49

Slide 49 text

ViewModel class TaskListViewModel( private val repository: TaskRepository ) { private val tasks = MutableLiveData>() fun getTasks(): LiveData> = tasks init { fetchTasks() } private fun fetchTasks() { tasks.value = repository.getTasks() } } @AdamMc331 #AndroidSummit 49

Slide 50

Slide 50 text

View class TaskListActivity : AppCompatActivity() { private val adapter = TaskAdapter() private val viewModel = TaskListviewModel(repository = InMemoryTaskService()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... subscribeToViewModel() } private fun subscribeToViewModel() { viewModel.getTasks().observe(this, Observer { tasks -> adapter.tasks = tasks }) } } @AdamMc331 #AndroidSummit 50

Slide 51

Slide 51 text

View class TaskListActivity : AppCompatActivity() { private val adapter = TaskAdapter() private val viewModel = TaskListviewModel(repository = InMemoryTaskService()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... subscribeToViewModel() } private fun subscribeToViewModel() { viewModel.getTasks().observe(this, Observer { tasks -> adapter.tasks = tasks }) } } @AdamMc331 #AndroidSummit 51

Slide 52

Slide 52 text

This Is Pretty Close To MVP, With One New Benefit @AdamMc331 #AndroidSummit 52

Slide 53

Slide 53 text

Since ViewModel Doesn't Reference View, We Can Leverage Android ViewModel To Outlast Config Changes @AdamMc331 #AndroidSummit 53

Slide 54

Slide 54 text

Handle Rotation In MVP 1. Update your presenter to save/restore state 2. Modify the view to call appropriate save/restore methods @AdamMc331 #AndroidSummit 54

Slide 55

Slide 55 text

Handle Rotation In MVP class TaskListContract { interface Presenter { // New: fun getState(): Bundle fun restoreState(bundle: Bundle?) } } @AdamMc331 #AndroidSummit 55

Slide 56

Slide 56 text

Handle Rotation In MVP class TaskListActivity : AppCompatActivity(), TaskListContract.View { override fun onCreate(savedInstanceState: Bundle?) { // ... presenter.restoreState(savedInstanceState) } override fun onSaveInstanceState(outState: Bundle) { outState.putAll(presenter.getState()) super.onSaveInstanceState(outState) } } @AdamMc331 #AndroidSummit 56

Slide 57

Slide 57 text

Handle Rotation In MVVM 1. Have ViewModel class extend the Android 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 #AndroidSummit 57

Slide 58

Slide 58 text

Handle Rotation In MVVM class TaskListViewModel( private val repository: TaskRepository ) : ViewModel() { // ... } @AdamMc331 #AndroidSummit 58

Slide 59

Slide 59 text

Handle Rotation In MVVM class TaskListActivity : AppCompatActivity() { private lateinit var viewModel: TaskListViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... setupViewModel() } private fun setupViewModel() { viewModel = ViewModelProviders.of(this, viewModelFactory).get(TaskListViewModel::class.java) viewModel.getTasks().observe(this, Observer { tasks -> taskAdapter.tasks = tasks }) } } @AdamMc331 #AndroidSummit 59

Slide 60

Slide 60 text

Is That Enough? • View does nothing but display data • Data fetching is all handled by model • ViewModel handles all UI logic • We can easily save state across config changes • Everything is separated, everything is testable • If you think this is good enough, use it! @AdamMc331 #AndroidSummit 60

Slide 61

Slide 61 text

Where Does MVVM Fall Short? @AdamMc331 #AndroidSummit 61

Slide 62

Slide 62 text

Let's Consider A More Complicated State @AdamMc331 #AndroidSummit 62

Slide 63

Slide 63 text

Let's Consider A More Complicated State sealed class TaskListState { object Loading : TaskListState() data class Loaded(val tasks: List) : TaskListState() data class Error(val error: Throwable?) : TaskListState() } @AdamMc331 #AndroidSummit 63

Slide 64

Slide 64 text

Let's Consider A More Complicated State class TaskListViewModel(private val repository: TaskRepository) : ViewModel() { init { showLoading() try { fetchTasks() } catch (e: Exception) { showError() } } // ... } @AdamMc331 #AndroidSummit 64

Slide 65

Slide 65 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(Throwable("Unable to fetch tasks.")) } } @AdamMc331 #AndroidSummit 65

Slide 66

Slide 66 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(Throwable("Unable to fetch tasks.")) } @AdamMc331 #AndroidSummit 66

Slide 67

Slide 67 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 #AndroidSummit 67

Slide 68

Slide 68 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 #AndroidSummit 68

Slide 69

Slide 69 text

Model-View-Intent @AdamMc331 #AndroidSummit 69

Slide 70

Slide 70 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 #AndroidSummit 70

Slide 71

Slide 71 text

The First Goal Is To Make Our State Changes Predictable @AdamMc331 #AndroidSummit 71

Slide 72

Slide 72 text

We Achieve This With A Reducer abstract class Reducer { abstract fun reduce(action: Action, state: State): State } @AdamMc331 #AndroidSummit 72

Slide 73

Slide 73 text

Clearly Defined Inputs And Outputs class TaskListReducer : Reducer() { override fun reduce(action: Action, state: TaskListState): TaskListState { return when (action) { is TaskListAction.TasksLoading -> TaskListState.Loading() is TaskListAction.TasksLoaded -> TaskListState.Loaded(action.tasks) is TaskListAction.TasksErrored -> TaskListState.Error() else -> state } } } @AdamMc331 #AndroidSummit 73

Slide 74

Slide 74 text

We Also Want A Single Source Of Truth @AdamMc331 #AndroidSummit 74

Slide 75

Slide 75 text

We Create A State Container Called A Store • Contains our state and exposes it for anyone to observe • Contains our reducer instance • Dispatches actions into that reducer to modify the state @AdamMc331 #AndroidSummit 75

Slide 76

Slide 76 text

Store Implementation class BaseStore( initialState: S, private val reducer: Reducer ) { private var stateListener: ((S) -> Unit)? = null private var currentState: S = initialState set(value) { field = value stateListener?.invoke(value) } fun dispatch(action: Action) { currentState = reducer.reduce(action, currentState) } fun subscribe(stateListener: ((S) -> Unit)?) { this.stateListener = stateListener } } @AdamMc331 #AndroidSummit 76

Slide 77

Slide 77 text

Store Implementation class BaseStore( initialState: S, private val reducer: Reducer ) { private var stateListener: ((S) -> Unit)? = null private var currentState: S = initialState set(value) { field = value stateListener?.invoke(value) } fun dispatch(action: Action) { currentState = reducer.reduce(action, currentState) } fun subscribe(stateListener: ((S) -> Unit)?) { this.stateListener = stateListener } } @AdamMc331 #AndroidSummit 77

Slide 78

Slide 78 text

Store Implementation class BaseStore( initialState: S, private val reducer: Reducer ) { private var stateListener: ((S) -> Unit)? = null private var currentState: S = initialState set(value) { field = value stateListener?.invoke(value) } fun dispatch(action: Action) { currentState = reducer.reduce(action, currentState) } fun subscribe(stateListener: ((S) -> Unit)?) { this.stateListener = stateListener } } @AdamMc331 #AndroidSummit 78

Slide 79

Slide 79 text

Store Implementation class BaseStore( initialState: S, private val reducer: Reducer ) { private var stateListener: ((S) -> Unit)? = null private var currentState: S = initialState set(value) { field = value stateListener?.invoke(value) } fun dispatch(action: Action) { currentState = reducer.reduce(action, currentState) } fun subscribe(stateListener: ((S) -> Unit)?) { this.stateListener = stateListener } } @AdamMc331 #AndroidSummit 79

Slide 80

Slide 80 text

Redux Diagram2 2 https://www.esri.com/arcgis-blog/products/3d-gis/3d-gis/react-redux-building-modern-web-apps-with-the-arcgis- js-api/ @AdamMc331 #AndroidSummit 80

Slide 81

Slide 81 text

Hook This Up To Our ViewModel/Presenter class TaskListViewModel(private val repository: TaskRepository) : ViewModel() { private val store: BaseStore = BaseStore( TaskListState.Loading(), TaskListReducer() ) // ... private fun fetchTasks() { store.dispatch(TaskListAction.TasksLoading) try { val tasks = repository.getTasks() store.dispatch(TaskListAction.TasksLoaded(tasks)) } catch (e: Throwable) { store.dispatch(TaskListAction.TasksErrored(e)) } } } @AdamMc331 #AndroidSummit 81

Slide 82

Slide 82 text

Hook This Up To Our ViewModel/Presenter class TaskListViewModel(private val repository: TaskRepository) : ViewModel() { private val store: BaseStore = BaseStore( TaskListState.Loading(), TaskListReducer() ) // ... private fun fetchTasks() { store.dispatch(TaskListAction.TasksLoading) try { val tasks = repository.getTasks() store.dispatch(TaskListAction.TasksLoaded(tasks)) } catch (e: Throwable) { store.dispatch(TaskListAction.TasksErrored(e)) } } } @AdamMc331 #AndroidSummit 82

Slide 83

Slide 83 text

Hook This Up To Our ViewModel/Presenter class TaskListViewModel(private val repository: TaskRepository) : ViewModel() { private val store: BaseStore = BaseStore( TaskListState.Loading(), TaskListReducer() ) // ... private fun fetchTasks() { store.dispatch(TaskListAction.TasksLoading) try { val tasks = repository.getTasks() store.dispatch(TaskListAction.TasksLoaded(tasks)) } catch (e: Throwable) { store.dispatch(TaskListAction.TasksErrored(e)) } } } @AdamMc331 #AndroidSummit 83

Slide 84

Slide 84 text

Is That Enough? • View does nothing but display data • Data fetching is all handled by model • ViewModel handles UI logic • We can easily save state across config changes • Everything is separated, everything is testable • State management is clear and predictable • If you think this is good enough, use it! @AdamMc331 #AndroidSummit 84

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

What Should I Take Away From This? @AdamMc331 #AndroidSummit 86

Slide 87

Slide 87 text

Model-View-Presenter • Separated concerns and allows us to unit test all of our code • Good for quick prototyping • Good for blog post samples because of its readability • Can handle config changes but requires a little more work • State management is unpredictable @AdamMc331 #AndroidSummit 87

Slide 88

Slide 88 text

Model-View-ViewModel • Separated concerns and allows us to unit test all of our code • Even better for quick prototyping • No contract class boilerplate • Good for blog post samples because of its readability3 • Can handle config changes easily if we use Android's architecture components • State management is unpredictable 3 Depending on how you expose information @AdamMc331 #AndroidSummit 88

Slide 89

Slide 89 text

Model-View-Intent • Can work with presenter or viewmodel • Separated concerns, testability come with this • Not good for quick prototyping • Can be confusing if used for sample apps due to unfamiliarity • Can handle config changes based on whether we used a presenter or a viewmodel • State management is clear and predictable @AdamMc331 #AndroidSummit 89

Slide 90

Slide 90 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 #AndroidSummit 90

Slide 91

Slide 91 text

What's Most Important • Be consistent @AdamMc331 #AndroidSummit 91

Slide 92

Slide 92 text

Thank you! https://github.com/adammc331/mvwtf @AdamMc331 #AndroidSummit 92