Refactoring Wunderlist. UA Mobile 2016

Preview:

Citation preview

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.

Recommended