Refactoring Wunderlist for Android
César Valiente
- Episode I. The presentation layer -
Who is this guy?
Image Placeholder
César Valiente Android Engineer @Wunderlist (@Microsoft)
Android Google Developer Expert (GDE)
+CesarValiente @CesarValiente
This is a story of…
evolution
growth
evolution
growth
simplicity
evolution
growth
simplicity
evolution
improvement
How is Wunderlist built?
How is Wunderlist built?
Android Layer
Presentation layer (UI and Android stuff)
Android project
How is Wunderlist built?
Android Layer
Presentation layer (UI and Android stuff)
Android project
Sync Layer
Model layer (Business logic)
Java project
How is Wunderlist built?
Android Layer
Presentation layer (UI and Android stuff)
Android project
Sync Layer
Model layer (Business logic)
Java project
Network Layer
Network layer (Accessing to the API data)
Java project
How is Wunderlist built?
Android Layer
Presentation layer (UI and Android stuff)
Android project
Sync Layer
Model layer (Business logic)
Java project
Network Layer
Network layer (Accessing to the API data)
Java project
Sync boundaries
How is Wunderlist built?
Android Layer
Presentation layer (UI and Android stuff)
Android project
Sync Layer
Model layer (Business logic)
Java project
Network Layer
Network layer (Accessing to the API data)
Java project
Sync boundaries Network boundaries
Dependency rule
Network
Dependency rule
Sync
Network
Dependency rule
Presentation
Sync
Network
Dependency rule
Presentation
Sync
Network
Dependency rule
The outer model knows the inner, not viceversa.
Problems?
Problems?Activities/Fragments become GOD classes.
Problems?Activities/Fragments become GOD classes.
Responsibilities are messed up.
Problems?Activities/Fragments become GOD classes.
Responsibilities are messed up.
Database in presentation layer (since it was our only Android related layer).
Problems?Activities/Fragments become GOD classes.
Responsibilities are messed up.
Difficult to test: framework dependencies.
Database in presentation layer (since it was our only Android related layer).
Problems?Activities/Fragments become GOD classes.
Responsibilities are messed up.
Difficult to test: framework dependencies.
Database in presentation layer (since it was our only Android related layer).
Cache in the sync layer.
Problems?Activities/Fragments become GOD classes.
Responsibilities are messed up.
Difficult to test: framework dependencies.
Database in presentation layer (since it was our only Android related layer).
Cache in the sync layer.
Complex data schemes used also in the presentation layer, not needed here.
Problems?Activities/Fragments become GOD classes.
Responsibilities are messed up.
Difficult to test: framework dependencies.
Database in presentation layer (since it was our only Android related layer).
Cache in the sync layer.
Complex data schemes used also in the presentation layer, not needed here.
We wanted to do it better!
What are we going to do?
What are we going to do?We are going to separate responsibilities. Yes, Even more!
What are we going to do?We are going to separate responsibilities. Yes, Even more!
We are going to decouple the different core elements of our app.
What are we going to do?We are going to separate responsibilities. Yes, Even more!
We are going to decouple the different core elements of our app.
We are going to improve the way we fetch data.
What are we going to do?We are going to separate responsibilities. Yes, Even more!
We are going to decouple the different core elements of our app.
We are going to improve the way we fetch data.
We are going to make testing easier to do, and increase our test coverage.
What are we going to do?We are going to separate responsibilities. Yes, Even more!
We are going to decouple the different core elements of our app.
We are going to improve the way we fetch data.
We are going to make testing easier to do, and increase our test coverage.
We are going to improve our codebase.
What are we going to do?We are going to separate responsibilities. Yes, Even more!
We are going to REFACTOR
We are going to decouple the different core elements of our app.
We are going to improve the way we fetch data.
We are going to make testing easier to do, and increase our test coverage.
We are going to improve our codebase.
Presentation layer
Presentation layer
App/UI
(Android project)
Presentation layer
App/UI
(Android project)
UI domain
(Java project)
Presentation layer
App/UI
(Android project)
UI domain
(Java project)
DB/Cache
(Android project)
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
MODEL
Business logic
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
MODEL
Business logic
User interacts with the app
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
MODEL
Business logic
Starts the process
User interacts with the app
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
MODEL
Business logic
Starts the process
Executes use caseUser interacts
with the app
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
MODEL
Business logic
Starts the process
Executes use case
Returns the result
User interacts with the app
Everything starts with the Model View Presenter (MVP) 😎
VIEW
Activity/Fragment
PRESENTER
Man in the middle
MODEL
Business logic
Starts the process
Executes use case
Updates view Returns the result
User interacts with the app
Everything starts with the Model View Presenter (MVP) 😎
VIEW PRESENTER
VIEW PRESENTER
VIEWCALLBACK
USE CASE
VIEW PRESENTER
VIEWCALLBACK
USE CASE
implements and passes it to the presenter
VIEW PRESENTER
VIEWCALLBACK
USE CASE
implements and passes it to the presenter
creates an instance and passes it to the
presenter
VIEW PRESENTER
VIEWCALLBACK
USE CASE
implements and passes it to the presenter
creates an instance and passes it to the
presenter
will execute it to start the process
VIEW PRESENTER
VIEWCALLBACK
USE CASE
implements and passes it to the presenter
creates an instance and passes it to the
presenter
will invoke it to communicate back (when we have
the result)
will execute it to start the process
ViewCallback
public interface ViewCallback { void showProgressDialog(); void hideProgressDialog(); void updateList(List content); }
View
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
private View progressDialog; private Adapter adapter; private SharingPresenter sharingPresenter; public void onCreate (Bundle onSavedInstanceState) { super.onCreate(onSavedInstanceState); bindViews(); setupPresenters(); }
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
private View progressDialog; private Adapter adapter; private SharingPresenter sharingPresenter; public void onCreate (Bundle onSavedInstanceState) { super.onCreate(onSavedInstanceState); bindViews(); setupPresenters(); }
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
private void setupPresenters () { GetListMembershipUseCase getListMembershipUseCase = new GetListMembershipUseCase( listId, appDataController); sharingPresenter = new SharingPresenter ( this, getListMembershipUseCase); }//——————— next slides ———————//
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
//——————————-— previous slide (setup) ———————————-—//
//——————— next slide (presenter invocation) ———————//
@Override void showProgressDialog() { progressDialog.show(); } @Override void hideProgressDialog() { progressDialog.hide(); } @Override void updateList(List<Memberships> list) { adapter.updateContent(list); } }
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
//——————————-— previous slide (setup) ———————————-—//
//——————— next slide (presenter invocation) ———————//
implements ViewCallback {
//——————————— slide 1 (setup) —————————-—//
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
//————— slide 2 (ViewCallback impl) ————-//
@OnClick private void showListMembers () { sharingPresenter.getListMembers(); }
//——————————— slide 1 (setup) —————————-—//
public class SharingFragmentActivity extends WLFragmentActivity implements ViewCallback {
//————— slide 2 (ViewCallback impl) ————-//
Presenter
public class SharingPresenter extends Presenter {
public class SharingPresenter extends Presenter {
private ViewCallback viewCallback; private GetListMembershipUseCase getListMembershipUseCase; public SharingPresenter ( ViewCallback viewCallback, GetListMembershipUseCase getListMembershipUseCase) { this.viewCallback = viewCallback; this.getListMembershipUseCase = getListMembershipUseCase; }
//——————— next slides ———————//
public class SharingPresenter extends Presenter {
//——————————-— previous slide (setup) ———————————-—//
@Override public void onStart() { EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); }
public class SharingPresenter extends Presenter {
//——————————-— previous slide (setup) ———————————-—//
@Override public void onStart() { EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); }
public class SharingPresenter extends Presenter {
//——————————-— previous slide (setup) ———————————-—//
public void onEventMainThread(ListMembershipEvent event) { viewCallback.hideProgressDialog(); viewCallback.updateList(event.getContent()); }
//——————— next slide (Use Case execution) ———————//
public class SharingPresenter extends Presenter {
//————————————- slide 1 (setup) ———————————————————-—//
//———————————— slide 2 (listening for events) ———————//
public void showListMembers () { viewCallback.showProgressDialog(); getListMembershipUseCase.execute(); } }
public class SharingPresenter extends Presenter {
//————————————- slide 1 (setup) ———————————————————-—//
//———————————— slide 2 (listening for events) ———————//
MODEL
MODEL
Use Cases
UI-Items, DB, Cache, etc.
MODEL
Use Cases
Use case
public class GetListMembershipUseCase implements UseCase<List<WLMembership>> {
public class GetListMembershipUseCase implements UseCase<List<WLMembership>> {
private String listId; private AppDataController appDataController; public GetListUseCase( String listId, AppDataController appDataController) { this.listId = listId; this.appDataController = appDataController; }
public class GetListMembershipUseCase implements UseCase<List<WLMembership>> {
private String listId; private AppDataController appDataController; public GetListUseCase( String listId, AppDataController appDataController) { this.listId = listId; this.appDataController = appDataController; } @Override public void execute() { appDataController.get(ApiObjectType.MEMBERSHIP, listId); } }
How were we fetching data?
LOADER
Activity/Fragment
DB
Presentation layer
1. Using loaders
LOADER
Activity/Fragment
DB
Presentation layer
1. Using loaders
LOADER
Activity/Fragment
DB
Presentation layer
1. Using loaders
LOADER
Activity/Fragment
BUS
Event or data is fired
Presentation layer
2. Using loaders + Bus
LOADER
Activity/Fragment
BUS
Subscription
Event or data is fired
Presentation layer
2. Using loaders + Bus
LOADER
Activity/Fragment
BUS
Subscription
Event or data is fired
Data
Presentation layer
2. Using loaders + Bus
LOADER
Activity/Fragment
BUS
Subscription Data
Event or data is fired
Data
Presentation layer
2. Using loaders + Bus
And how do we fetch data now? (an evolution of approaches)
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
3
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
3
4
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
5
3
4
1. Use case that returns data. Synchronous.
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
5
6
3
4
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
3
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
3
4
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
5
3
4
2. Use case using callback. Asynchronous.
2
Presenter
View
Presentation layer Model
UI domaindb/
cache
Use Case
1
2
5
6
3
4
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
BUS
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
1
BUS
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
1
2
BUS
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
1
2
BUS
3
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
1
2
BUS
3
4
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
1
2
BUS
3
45
3. Use case using an event bus.
Presenter
View
Presentation layer Model
UI domain
db/cache
Use Case
1
2
6
BUS
3
45
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
VIEW
PRESENTER
OBSERVER (USE CASE)
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
VIEW
PRESENTER
OBSERVER
1
(USE CASE)
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
VIEW
PRESENTER
OBSERVER
1
2
(USE CASE)
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
VIEW
PRESENTER
OBSERVER
3
1
2
(USE CASE)
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
VIEW
PRESENTER
OBSERVER
3
4
1
2
(USE CASE)
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
VIEW
PRESENTER
OBSERVER5
3
4
1
2
(USE CASE)
4. Use case using RxJavaModel
UI domain
db/cache
OBSERVABLE
Presentation layer
6
VIEW
PRESENTER
OBSERVER5
3
4
1
2
(USE CASE)
Testing?
Testing? Views now don’t have business logic and/or data.
Testing? Views now don’t have business logic and/or data.
Presenters don’t have framework dependencies.
Testing? Views now don’t have business logic and/or data.
Presenters don’t have framework dependencies.
Dependency injection make testing easier
Testing? Views now don’t have business logic and/or data.
Presenters don’t have framework dependencies.
Use cases are very small and simple.
Dependency injection make testing easier
Testing? Views now don’t have business logic and/or data.
Presenters don’t have framework dependencies.
Use cases are very small and simple.
We can test now every component independently.
Dependency injection make testing easier
Testing? Views now don’t have business logic and/or data.
Presenters don’t have framework dependencies.
Use cases are very small and simple.
We can test now every component independently.
Dependency injection make testing easier
Now testing is easy peasy!
More testing?
More testing?
VIEW
Espresso
Java + Android
Mockito
JUnit
More testing?
VIEW
Espresso
Java + Android
Mockito
JUnit
PRESENTER
TestSubscriber
Mockito
JUnit
Java + RxJava
More testing?
VIEW
Espresso
Java + Android
Mockito
JUnit
PRESENTER
TestSubscriber
Mockito
JUnit
Java + RxJava
USE CASE
TestSubscriber
Mockito
JUnit
Java + RxJava
More testing?
VIEW
Espresso
Java + Android
Mockito
JUnit
PRESENTER
TestSubscriber
Mockito
JUnit
Java + RxJava
USE CASE
TestSubscriber
Mockito
JUnit
Java + RxJava
UI-DOMAIN
TestSubscriber
Mockito
JUnit
Java + RxJava
More testing?
VIEW
Espresso
Java + Android
Mockito
JUnit
PRESENTER
TestSubscriber
Mockito
JUnit
Java + RxJava
USE CASE
TestSubscriber
Mockito
JUnit
Java + RxJava
UI-DOMAIN
TestSubscriber
Mockito
JUnit
Java + RxJava
DB/CACHEJava + Android
+ RxJava
Robolectric
Mockito
JUnit
TestSubscriber
Lessons learned
Lessons learnedDependency Inversion Principle is key.
Lessons learnedDependency Inversion Principle is key.
Following SOLID principles makes MVP easy to build.
Lessons learnedDependency Inversion Principle is key.
Following SOLID principles makes MVP easy to build.
Small use cases define what you want to do.
Lessons learnedDependency Inversion Principle is key.
Following SOLID principles makes MVP easy to build.
Small use cases define what you want to do.We have different alternatives to fetch data, choose yours.
Lessons learnedDependency Inversion Principle is key.
Following SOLID principles makes MVP easy to build.
Small use cases define what you want to do.We have different alternatives to fetch data, choose yours.
Decouple components makes testing easier.
?+CesarValiente @CesarValiente
Thanks!
?
+CesarValiente @CesarValiente
Thanks!
License
(cc) 2016 César Valiente. Some rights reserved. This document is distributed under the Creative Commons Attribution-ShareAlike 3.0 license, available in http://creativecommons.org/licenses/by-sa/3.0/
Image licenses
Wunderlist (Microsoft): permission granted.
Emojis by Emoji One (CC-BY): http://emojione.com/
Iceberg: http://science-all.com/iceberg.html
All images belong to their owners.