Entities Use Cases Controllers Gateways Presenters Devices Web DB UI External Interfaces Enterprise Business Rules Interface Adapters Frameworks & Drivers Application Business Rules
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” }
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 )
M is for Model Entities Use Cases Controllers Gateways Presenters Devices Web DB UI External Interfaces interface Model { fun getItems(): Observable> }
–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.”
Testing MVC @Test fun test_getItemsError_notifiesError() { val items = ItemListFactory.makeList() val model = ModelImpl(items) val view = TestViewImpl() val controller = ListController(model, view)
MVC Takeaways User Controller View Model • Controller has too many responsibilities. • The View knows about the Model and the Controller. • Testing could be easier.
User Presenter View Model interface IItemListView { fun setLoading(loading: Boolean) fun setItems(items: List) } class ItemListViewPresenter { fun attach(view: IItemListView) { this.view = view }
User Presenter View Model interface IItemListView { fun setLoading(loading: Boolean) fun setItems(items: List) } class ItemListViewPresenter { fun attach(view: IItemListView) { this.view = view }
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)
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.
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.
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}
MVVM Takeaways • The ViewModel is still the “middle man” • Clearly defined code paths • The View observes changes in the ViewModel User ViewModel View Model
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
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
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
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
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
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
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
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
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
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
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
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
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
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