Slide 1

Slide 1 text

MV–WTF? Kevin Carpenter Notion AI

Slide 2

Slide 2 text

Pull to Refresh List

Slide 3

Slide 3 text

Entities Use Cases Controllers Gateways Presenters Devices Web DB UI External Interfaces Enterprise Business Rules Interface Adapters Frameworks & Drivers Application Business Rules

Slide 4

Slide 4 text

M is for Model Entities Use Cases Controllers Gateways Presenters Devices Web DB UI External Interfaces GET https://hacker-news.com/item/11595667.json { "by": "jamesblonde", "descendants": 0, "id": 11595667, "score": 2, "time": 1461937655, "title": “Make email great again”, "type": "story", "url": “https:\/\/email.net/great” }

Slide 5

Slide 5 text

M is for Model Entities Use Cases Controllers Gateways Presenters Devices Web DB UI External Interfaces { "id": 11595667, "by": "jamesblonde", "descendants": 0, "score": 2, "time": 1461937655, "title": "Successful way to stop spam email from ResearchGate", "type": "story", "url": "https:\/\/www.researchgate.net\/post" } CREATE TABLE IF NOT EXISTS ItemTable( id TEXT PRIMARY KEY NOT NULL, by TEXT NOT NULL, descendants INTEGER NOT NULL, score INTEGER NOT NULL, time INTEGER NOT NULL, title TEXT NOT NULL, type TEXT NOT NULL, url TEXT NOT NULL )

Slide 6

Slide 6 text

M is for Model Entities Use Cases Controllers Gateways Presenters Devices Web DB UI External Interfaces interface Model {
 fun getItems(): Observable>
 }

Slide 7

Slide 7 text

V is for View Devices Web DB UI External Interfaces

Slide 8

Slide 8 text

WTF is for… Controllers Presenters Devices Web DB UI External Interfaces Presentation Models model.getItems()

Slide 9

Slide 9 text

MV–Why?

Slide 10

Slide 10 text

Single Responsibility Principle

Slide 11

Slide 11 text

–“Uncle” Bob Martin “Gather together the things that change for the same reasons. Separate those things that change for different reasons.”

Slide 12

Slide 12 text

Testing Unit

Slide 13

Slide 13 text

Testing JU Unit Integration

Slide 14

Slide 14 text

Testing JU Unit Integration UI

Slide 15

Slide 15 text

MV-How?

Slide 16

Slide 16 text

Separated Presentation

Slide 17

Slide 17 text

–Martin Fowler “Ensure that any code that manipulates presentation only manipulates presentation, pushing all domain and data source logic into clearly separated areas of the program.”

Slide 18

Slide 18 text

MVC “Supervising Controller” User Controller View Model

Slide 19

Slide 19 text

class ItemListViewImpl :
 RecyclerView, ItemListView, OnRefreshListener {
 // lifecycle, constructor, etc…
 
 // OnRefreshListener implementation
 override fun onRefresh() {
 setLoadingAnimation(true)
 controller.refreshItems()
 }
 } User Controller View Model

Slide 20

Slide 20 text

User Controller View Model class ItemListViewImpl :
 RecyclerView, ItemListView, OnRefreshListener {
 // lifecycle, constructor, etc…
 
 // OnRefreshListener implementation
 override fun onRefresh() {
 setLoadingAnimation(true)
 controller.refreshItems()
 }
 }

Slide 21

Slide 21 text

class Controller() {
 
 @Inject
 private lateinit var model: Model
 
 private var view: IItemListView? = null
 
 fun refreshItems() {
 model.updateItems()
 .subscribe(
 { view?.notifyUpdated() },
 { view?.notifyError() })
 }
 } User Controller View Model

Slide 22

Slide 22 text

User Controller View Model class Controller() {
 
 @Inject
 private lateinit var model: Model
 
 private var view: IItemListView? = null
 
 fun refreshItems() {
 model.updateItems()
 .subscribe { view?.notifyUpdated() }
 }
 }

Slide 23

Slide 23 text

class Controller() {
 
 @Inject
 private lateinit var model: Model
 
 private var view: IItemListView? = null
 
 fun refreshItems() {
 model.updateItems()
 .subscribe { view?.notifyUpdated() }
 }
 } User Controller View Model

Slide 24

Slide 24 text

class ItemListViewImpl :
 RecyclerView, ItemListView, OnRefreshListener {
 // lifecycle, constructor, etc…
 
 // OnRefreshListener implementation
 override fun onRefresh() {/*...*/}
 
 fun notifyUpdated() {
 val items = model.getItems()
 adapter.items = items
 setLoadingAnimation(false)
 }
 } User Controller View Model

Slide 25

Slide 25 text

class ItemListViewImpl :
 RecyclerView, ItemListView, OnRefreshListener {
 // lifecycle, constructor, etc…
 
 // OnRefreshListener implementation
 override fun onRefresh() {/*...*/}
 
 fun notifyUpdated() {
 val items = model.getItems()
 adapter.items = items
 setLoadingAnimation(false)
 }
 } User Controller View Model

Slide 26

Slide 26 text

class ItemListViewImpl :
 RecyclerView, ItemListView, OnRefreshListener {
 // lifecycle, constructor, etc…
 
 // OnRefreshListener implementation
 override fun onRefresh() {/*...*/}
 
 fun notifyUpdated() {
 val items = model.getItems()
 adapter.items = items
 setLoadingAnimation(false)
 }
 } User Controller View Model

Slide 27

Slide 27 text

Testing MVC class TestViewImpl(private val model: Model) : IItemListView {
 var items = emptyList() var loading = true 
 override fun notifyUpdated() {
 this.items = model.getItems() this.loading = false
 }
 }
 
 class ModelImpl(private val items: List): Model {
 override fun fetchItems(): ListItems {
 return Observable.just(items)
 }
 
 override fun getItems(): List {
 return items
 }
 }

Slide 28

Slide 28 text

Testing MVC class TestViewImpl(private val model: Model) : IItemListView {
 var items = emptyList() var loading = true override fun notifyUpdated() {
 this.items = model.getItems() this.loading = false
 }
 }
 
 class ModelImpl(private val items: List): Model {
 override fun fetchItems(): ListItems {
 return Observable.just(items)
 }
 
 override fun getItems(): List {
 return items
 }
 }

Slide 29

Slide 29 text

Testing MVC @Test
 fun test_getItemsError_notifiesError() {
 val items = ItemListFactory.makeList()
 val model = ModelImpl(items)
 val view = TestViewImpl()
 val controller = ListController(model, view)
 
 controller.refreshItems() // assume blocking
 assertTrue(view.items = items)
 assertFalse(view.loading)
 }

Slide 30

Slide 30 text

MVC Takeaways User Controller View Model • Controller has too many responsibilities.

Slide 31

Slide 31 text

MVC Takeaways User Controller View Model • Controller has too many responsibilities. • The View knows about the Model and the Controller.

Slide 32

Slide 32 text

MVC Takeaways User Controller View Model • Controller has too many responsibilities. • The View knows about the Model and the Controller. • Testing could be easier.

Slide 33

Slide 33 text

Are Fragments Controllers?

Slide 34

Slide 34 text

MVP “Passive View” User Presenter View Model

Slide 35

Slide 35 text

User Presenter View class ItemListView : RecyclerView,
 IItemListView, OnRefreshListener {
 
 override fun onRefresh() {
 presenter.onRefresh()
 }
 } Model

Slide 36

Slide 36 text

Model User Presenter View class ItemListView : RecyclerView,
 IItemListView, OnRefreshListener {
 
 override fun onAttachedToWindow() {
 super.onAttachedToWindow()
 presenter.attach(this)
 }
 
 override fun onDetachedFromWindow() {
 presenter.detach()
 super.onDetachedFromWindow()
 }
 
 override fun onRefresh() {
 presenter.onRefresh()
 }
 }

Slide 37

Slide 37 text

Model User Presenter View class ItemListView : RecyclerView,
 IItemListView, OnRefreshListener {
 
 override fun onAttachedToWindow() {
 super.onAttachedToWindow()
 presenter.attach(this)
 }
 
 override fun onDetachedFromWindow() {
 presenter.detach()
 super.onDetachedFromWindow()
 }
 
 override fun onRefresh() {
 presenter.onRefresh()
 }
 }

Slide 38

Slide 38 text

User Presenter View Model interface IItemListView {
 fun setLoading(loading: Boolean)
 fun setItems(items: List)
 } class ItemListViewPresenter {
 fun attach(view: IItemListView) {
 this.view = view
 }
 
 fun detach() {
 this.view = null
 }
 
 fun onRefresh() {
 view?.setLoading(true)
 model.getItems()
 .subscribe(
 { items -> view?.setItems(items) },//onNext
 { view?.setLoading(false) }, //onError
 { view?.setLoading(false) })//onComplete }
 }

Slide 39

Slide 39 text

User Presenter View Model interface IItemListView {
 fun setLoading(loading: Boolean)
 fun setItems(items: List)
 } class ItemListViewPresenter {
 fun attach(view: IItemListView) {
 this.view = view
 }
 
 fun detach() {
 this.view = null
 }
 
 fun onRefresh() {
 view?.setLoading(true)
 model.getItems()
 .subscribe(
 { items -> view?.setItems(items) },//onNext
 { view?.setLoading(false) }, //onError
 { view?.setLoading(false) })//onComplete }
 }

Slide 40

Slide 40 text

Testing MVP @Test
 fun test_refreshItems_setsViewItems() {
 val items = ItemListFactory.makeList()
 val model = mock(Model::class.java)
 mockWhen(model.getItems()).thenReturn(Observable.just(items))
 
 val view = mock(IItemListView::class.java)
 val presenter = ItemListViewPresenter(model, view)
 
 presenter.refreshItems() // assume blocking
 verify(view).setItems(items)
 verify(view).setLoading(false)
 }

Slide 41

Slide 41 text

MVP Takeaways • The Presenter has the same responsibilities as before. User Presenter View Model

Slide 42

Slide 42 text

MVP Takeaways User Presenter View Model • The Presenter has the same responsibilities as before. • The View no longer knows about the Model.

Slide 43

Slide 43 text

MVP Takeaways User Presenter View Model • Presenter has the same responsibilities as before. • View no longer knows about the Model. • Presenter is in full control of what is rendered in View.

Slide 44

Slide 44 text

MVP Takeaways User Presenter View Model • Presenter has the same responsibilities as before. • View no longer knows about the Model. • Presenter is in full control of what is rendered in View. • Testing is still tough. We need to thoroughly mock IItemListView to unit test.

Slide 45

Slide 45 text

Presentation Model

Slide 46

Slide 46 text

MVP View stores its own state User Presenter View Model class ItemListView : RecyclerView,
 IItemListView, OnRefreshListener {
 
 private var items: List = emptyList()
 private var loading: Boolean = false
 
 override fun setItems(newItems: List) {
 this.items = newItems
 // update items
 }
 
 override fun setLoading(loading: Boolean) {
 this.loading = loading
 // show or hide progress bar
 }
 }

Slide 47

Slide 47 text

MVP View stores its own state User Presenter View Model class ItemListView : RecyclerView,
 IItemListView, OnRefreshListener {
 
 private var items: List = emptyList()
 private var loading: Boolean = false
 
 override fun onSaveInstanceState(): Parcelable {
 // ☠☠☠☠☠☠☠☠
 }
 
 override fun setItems(newItems: List) {
 this.items = newItems
 }
 
 override fun setLoading(loading: Boolean) {
 this.loading = loading
 }
 }

Slide 48

Slide 48 text

Presentation Model stores state User PM View Model class ItemListViewModel {
 private var model: Model
 
 private var loading: Boolean
 private var items: List
 
 fun onRefresh() {
 loading = true
 items = model.getItems()
 // communicate to View
 }
 }

Slide 49

Slide 49 text

MVVM Model-View-ViewModel User ViewModel View Model

Slide 50

Slide 50 text

User ViewModel View Model class ItemListView: RecyclerView, OnRefreshListener { override fun onRefresh() {
 viewModel.onRefresh()
 }
 }

Slide 51

Slide 51 text

Model User ViewModel View class ItemListView: RecyclerView, OnRefreshListener { override fun onRefresh() {
 viewModel.onRefresh()
 }
 }

Slide 52

Slide 52 text

User ViewModel View class ItemListViewModel {
 private var model: Model
 
 private var loadingSubject = PublishSubject.create itemsSubject.onNext(items) }, // onNext { loadingSubject.onNext(false) }, // onError
 { loadingSubject.onNext(false) }) // onComplete
 }
 } Model

Slide 53

Slide 53 text

User ViewModel View Model class ItemListViewModel {
 private var model: Model
 
 private var loadingSubject = PublishSubject.create itemsSubject.onNext(items) }, // onNext { loadingSubject.onNext(false) }, // onError
 { loadingSubject.onNext(false) }) // onComplete
 }
 }

Slide 54

Slide 54 text

User ViewModel View class ItemListView {
 
 override fun onAttachedToWindow() {
 super.onAttachedToWindow()
 
 viewModel.loadingSubject
 .subscribe(loadingObserver)
 
 viewModel.itemsSubject
 .subscribe(itemsObserver)
 }
 } Model

Slide 55

Slide 55 text

User ViewModel View class ItemListView {
 
 private val loadingObserver = { loading: Boolean ->
 progressBar.visibility = if (loading) {
 View.VISIBLE
 }
 else {
 View.GONE
 }
 }
 
 private val itemsObserver = { items: List ->
 adapter.items = items
 }
 } Model

Slide 56

Slide 56 text

Testing MVVM @Test
 fun test_getItems_updatesItems() {
 val items = ItemListFactory.makeList()
 val model = mock(Model::class.java)
 mockWhen(model.getItems()).thenReturn(Observable.just(items))
 val viewModel = ItemListViewModel(model)
 
 var updatedItems = emptyList()
 val itemsSubscriber = { newItems -> items = newItems }
 var loading = true
 val loadingSubscriber = { loadingUpdate -> loading = loadingUpdate}
 
 viewModel.itemsSubject.subscribe(itemsSubscriber)
 viewModel.loadingSubject.subscribe(loadingSubscriber)
 
 viewModel.refreshItems() // assume blocking
 assertEquals(items, updatedItems)
 assertFalse(loading)
 }

Slide 57

Slide 57 text

MVVM Takeaways • The ViewModel is still the “middle man” User ViewModel View Model

Slide 58

Slide 58 text

MVVM Takeaways • The ViewModel is still the “middle man” • Clearly defined code paths User ViewModel View Model

Slide 59

Slide 59 text

MVVM Takeaways • The ViewModel is still the “middle man” • Clearly defined code paths • The View observes changes in the ViewModel User ViewModel View Model

Slide 60

Slide 60 text

MVVM Takeaways • The ViewModel is still the “middle man” • Clearly defined code paths • The View observes changes in the ViewModel • Unit testing is improved! User ViewModel View Model

Slide 61

Slide 61 text

Data Binding class User(
 var firstName: String,
 var lastName: String) class UserView {
 private val userBinding
 
 constructor(/*...*/) {
 val inflater = LayoutInflater.from(context)
 userBinding = UserViewBinding.inflate(inflater)
 }
 
 fun updateUser(newUser: User) {
 userBinding.update(newUser)
 }
 
 }

Slide 62

Slide 62 text

Data Binding 
 
 
 
 
 
 
 
 
 


Slide 63

Slide 63 text

Now what?

Slide 64

Slide 64 text

Cycle.js http://cycle.js.org/

Slide 65

Slide 65 text

Sources • External read effects time

Slide 66

Slide 66 text

Sources • External read effects • User input time ☝ ☝ ☝

Slide 67

Slide 67 text

Sources • External read effects • User input • E.g. Pull to Refresh Refresh Events time

Slide 68

Slide 68 text

Sinks • Write effects to external world time

Slide 69

Slide 69 text

Sinks • Write effects to external world • Data model output time

Slide 70

Slide 70 text

Sinks • Write effects to external world • Data model output • E.g. formatted data response, like our list of Items Item List Update Events time

Slide 71

Slide 71 text

Cycle • UIs are cycles Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 ☝

Slide 72

Slide 72 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 73

Slide 73 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 74

Slide 74 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 75

Slide 75 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 76

Slide 76 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions • UIs are asynchronous Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 77

Slide 77 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions • UIs are asynchronous Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 78

Slide 78 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions • UIs are asynchronous Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 79

Slide 79 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions • UIs are asynchronous Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 80

Slide 80 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions • UIs are asynchronous Pull to Refresh List Updates ViewModel: f(refresh) = list updates

Slide 81

Slide 81 text

Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 Pull to Refresh List Updates ViewModel: f(refresh) = list updates • UIs are cycles • UIs are functions • UIs are asynchronous Cycle

Slide 82

Slide 82 text

Cycle Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 • UIs are cycles • UIs are functions • UIs are asynchronous • UIs are symmetric ViewModel: f(refresh) = list updates

Slide 83

Slide 83 text

Cycle • UIs are cycles • UIs are functions • UIs are asynchronous • UIs are symmetric • The user is a function Source: What if the user was a function? by Andre Staltz at JSConf Budapest 2015 ViewModel: f(refresh) = list updates

Slide 84

Slide 84 text

RPM Reactive Presentation Model User Reactive Presentation Model Model View

Slide 85

Slide 85 text

interface Model {
 fun setUp(sources: I): O
 
 fun tearDown()
 
 interface Sources
 
 interface Sinks
 } User Reactive Presentation Model Model View

Slide 86

Slide 86 text

class Sources(
 val refreshEvents: Observable) : Sources User Reactive Presentation Model Model View

Slide 87

Slide 87 text

class Sinks(
 val items: Observable>,
 val loading: Observable) : Sinks User Reactive Presentation Model Model View

Slide 88

Slide 88 text

class ItemListViewModel : Model {
 class Sources(
 val refreshEvents: Observable) : Sources
 
 class Sinks(
 val items: Observable>,
 val loading: Observable) : Sinks
 } User Reactive Presentation Model Model View

Slide 89

Slide 89 text

class ItemListView : RecyclerView, OnRefreshListener {
 
 @Inject
 private lateinit var model: ItemListViewModel
 
 private val progressBar //...
 private val adapter = ItemListViewAdapter()
 private val subscriptions = CompositeSubscription()
 private val refreshSubject = PublishSubject.create()
 
 override fun onAttachedToWindow() {
 super.onAttachedToWindow()
 val sinks = model.setUp(Sources(refreshSubject.asObservable()))
 sinks.loading.subscribe(loadingObserver).addToSubscriptions()
 sinks.items.subscribe(itemsObserver).addTo(subscriptions)
 }
 } User Reactive Presentation Model Model View

Slide 90

Slide 90 text

Clean News github.com/pardom/CleanNews

Slide 91

Slide 91 text

class ItemListViewModel : Model {
 
 class Sources(
 val itemUrlClicks: Observable,
 val itemDetailClicks: Observable) : Model.Sources
 
 class Sinks(val items: Observable>) : Model.Sinks
 }


Slide 92

Slide 92 text

class ItemDetailViewModel : Model {
 class Sources(
 val itemUrlClicks: Observable,
 val itemDetailClicks: Observable) : Model.Sources
 
 class Sinks(val items: Observable>) : Model.Sinks
 }

Slide 93

Slide 93 text

class ItemDetailViewModel : Model {
 
 class Sources(
 val backClicks: Observable,
 val shareClicks: Observable) : Model.Sources
 
 class Sinks(
 val item: Observable,
 val children: Observable>) : Model.Sinks
 }

Slide 94

Slide 94 text

What should I use?

Slide 95

Slide 95 text

Questions? @kevcarpenter | www.kevcar.me/mvwtf