Slide 1

Slide 1 text

From mono to clean @fmendes6

Slide 2

Slide 2 text

Filipe Mendes • Android Developer • GDG Lisbon Organizer • Sharednode.org • @fmendes6 • www.fmendes6.com

Slide 3

Slide 3 text

Disclaimer This presentation focuses on how I evolved from a monolithic architecture to a clean architecture with RxJava based on Fernando Cejas proposal for a single use-case. This presentation assumes you have used retrofit in the past but haven’t tried RxJava or the MVP (model-view-presenter) pattern yet. Keep in mind that the solutions I will present here are based on my experiences and my failed attempts to create a better and scalable architecture.

Slide 4

Slide 4 text

Disclaimer There are many great blog posts and talks about this topic but very few are easy to comprehend for beginners. I will, too, quote several paragraphs from different blog posts that were key for me to understand these concepts but I highly encourage you to read them entirely. Therefore, all the credits go to them and everyone else responsible to move the android community forward.

Slide 5

Slide 5 text

Let’s get started.

Slide 6

Slide 6 text

Use Case Using the fake public api http://jsonplaceholder.typicode.com/ we will do the following: 1. Create an activity 2. Do an http request to get the detail of one post.
 (/posts/1) { 
 "userId": 1,
 "id": 1,
 "title": “this is a title",
 "body": “this is a detailed message of the post…”
 }

Slide 7

Slide 7 text

Use Case 3. Do another request to get additional comments on that post. 
 (/posts/1/comments) 4. Parse it to java objects. 5. Show it in an Activity.

Slide 8

Slide 8 text

Monolithic architecture • The whole application is implemented in a single app module. • It’s usually fast to get things working. • Activities are responsible to get the data they need as well as presenting it.

Slide 9

Slide 9 text

retrofit Monolithic architecture HomeActivity Business Logic
 User Interface
 Data Sources Retrofit domain
 model app

Slide 10

Slide 10 text

public class MainActivity extends AppCompatActivity {
 
 private PostApi mApi;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mApi = ApiProvider.getApi().create(PostApi.class); loadPost(1); //consider this in onStart
 } 
 private void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 @Override
 public void success(Post post, Response response) {
 //TODO bind the content of the post to the layout here
 Toast.makeText(MainActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 loadComments(postId);
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 
 private void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 //TODO bind the content of the contents to the layout here
 Toast.makeText(MainActivity.this,"total comments="+commentList.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 } HomeActivity Retrofit

Slide 11

Slide 11 text

public class MainActivity extends AppCompatActivity {
 
 private PostApi mApi;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mApi = ApiProvider.getApi().create(PostApi.class); loadPost(1); //consider this in onStart
 }
 
 private void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 @Override
 public void success(Post post, Response response) {
 //TODO bind the content of the post to the layout here
 Toast.makeText(MainActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 loadComments(postId);
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 
 private void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 //TODO bind the content of the contents to the layout here
 Toast.makeText(MainActivity.this,"total comments="+commentList.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 } HomeActivity Retrofit

Slide 12

Slide 12 text

public class MainActivity extends AppCompatActivity {
 
 private PostApi mApi;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mApi = ApiProvider.getApi().create(PostApi.class); loadPost(1); //consider this in onStart
 }
 
 private void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 @Override
 public void success(Post post, Response response) {
 //TODO bind the content of the post to the layout here
 Toast.makeText(MainActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 loadComments(postId);
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 
 private void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 //TODO bind the content of the contents to the layout here
 Toast.makeText(MainActivity.this,"total comments="+commentList.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 } HomeActivity Retrofit

Slide 13

Slide 13 text

public class MainActivity extends AppCompatActivity {
 
 private PostApi mApi;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mApi = ApiProvider.getApi().create(PostApi.class); loadPost(1); //consider this in onStart
 }
 
 private void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 @Override
 public void success(Post post, Response response) {
 //TODO bind the content of the post to the layout here
 Toast.makeText(MainActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 loadComments(postId);
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 
 private void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 //TODO bind the content of the comments to the layout here
 Toast.makeText(MainActivity.this,"total comments="+commentList.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 } HomeActivity Retrofit

Slide 14

Slide 14 text

public class MainActivity extends AppCompatActivity {
 
 private PostApi mApi;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mApi = ApiProvider.getApi().create(PostApi.class); loadPost(1); //consider this in onStart
 }
 
 private void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 @Override
 public void success(Post post, Response response) {
 //TODO bind the content of the post to the layout here
 Toast.makeText(MainActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 loadComments(postId);
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 
 private void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 //TODO bind the content of the contents to the layout here
 Toast.makeText(MainActivity.this,"total comments="+commentList.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void failure(RetrofitError error) {
 //TODO handle error
 }
 });
 }
 } HomeActivity Retrofit

Slide 15

Slide 15 text

Monolithic architecture • Can get very messy to implement complex use-cases. • Activities contain a lot of application logic (and sometimes the logic is repeated across activities). • It is nearly impossible to test each feature separately (http request, UI views behaviour, etc.) • Very error prone. • Doesn’t scale very well.

Slide 16

Slide 16 text

Solution 1 Model View Presenter

Slide 17

Slide 17 text

MVP architecture HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources domain
 model Retrofit app

Slide 18

Slide 18 text

Model

Slide 19

Slide 19 text

Model public class Post implements Parcelable {
 
 private int userId;
 private int id;
 private String title;
 private String body;
 
 public int getUserId() {
 return userId;
 }
 public int getId() {
 return id;
 }
 public String getTitle() {
 return title;
 }
 public String getBody() {
 return body;
 }
 
 
 protected Post(Parcel in) {
 userId = in.readInt();
 id = in.readInt();
 title = in.readString();
 body = in.readString();
 } … domain
 model

Slide 20

Slide 20 text

View

Slide 21

Slide 21 text

View public interface IMainView {
 
 void onPostLoaded(Post post);
 
 void onCommentsLoaded(List comments);
 
 void onError(String error);
 }
 HomeActivity HomePresenter IHomeView Retrofit

Slide 22

Slide 22 text

Activity

Slide 23

Slide 23 text

public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 private HomePresenter mPresenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mPresenter = new HomePresenter(this); mPresenter.loadPost(1); //consider this in onStart
 }
 
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 mPresenter.loadComments(post.getId());
 }
 
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onError(String error) {
 //TODO handle error
 }
 } HomeActivity HomePresenter IHomeView Retrofit Activity

Slide 24

Slide 24 text

Activity public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 private HomePresenter mPresenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mPresenter = new HomePresenter(this); mPresenter.loadPost(1); //consider this in onStart
 }
 
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 mPresenter.loadComments(post.getId());
 }
 
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onError(String error) {
 //TODO handle error
 }
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 25

Slide 25 text

Activity public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 private HomePresenter mPresenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mPresenter = new HomePresenter(this); mPresenter.loadPost(1); //consider this in onStart
 }
 
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 mPresenter.loadComments(post.getId());
 }
 
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onError(String error) {
 //TODO handle error
 }
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 26

Slide 26 text

Activity public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 private HomePresenter mPresenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mPresenter = new HomePresenter(this); mPresenter.loadPost(1); //consider this in onStart
 }
 
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 mPresenter.loadComments(post.getId());
 }
 
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onError(String error) {
 //TODO handle error
 }
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 27

Slide 27 text

Activity public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 private HomePresenter mPresenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mPresenter = new HomePresenter(this); mPresenter.loadPost(1); //consider this in onStart
 }
 
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 mPresenter.loadComments(post.getId());
 }
 
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onError(String error) {
 //TODO handle error
 }
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 28

Slide 28 text

Activity public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 private HomePresenter mPresenter;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 mPresenter = new HomePresenter(this); mPresenter.loadPost(1); //consider this in onStart
 } 
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT).show();
 mPresenter.loadComments(post.getId());
 }
 
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 }
 
 @Override
 public void onError(String error) {
 //TODO handle error
 }
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 29

Slide 29 text

Presenter

Slide 30

Slide 30 text

public class HomePresenter {
 
 private PostApi mApi;
 private IHomeView mView;
 
 public HomePresenter(IHomeView view) {
 mApi = ApiProvider.getApi().create(PostApi.class);
 mView = view;
 }
 
 public void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 public void success(Post post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 
 
 public void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter

Slide 31

Slide 31 text

public class HomePresenter {
 
 private PostApi mApi;
 private IHomeView mView;
 
 public HomePresenter(IHomeView view) {
 mApi = ApiProvider.getApi().create(PostApi.class);
 mView = view;
 }
 
 public void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 public void success(Post post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 
 
 public void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter

Slide 32

Slide 32 text

public class HomePresenter {
 
 private PostApi mApi;
 private IHomeView mView;
 
 public HomePresenter(IHomeView view) {
 mApi = ApiProvider.getApi().create(PostApi.class);
 mView = view;
 }
 
 public void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 public void success(Post post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 
 
 public void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter

Slide 33

Slide 33 text

public class HomePresenter {
 
 private PostApi mApi;
 private IHomeView mView;
 
 public HomePresenter(IHomeView view) {
 mApi = ApiProvider.getApi().create(PostApi.class);
 mView = view;
 }
 
 public void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 public void success(Post post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 
 
 public void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter

Slide 34

Slide 34 text

Solution architecture HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources domain
 model Retrofit app

Slide 35

Slide 35 text

Solution 1 flow public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 @Override
 protected void onStart() {
 super.onStart();
 mPresenter.loadPost(1);
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 36

Slide 36 text

Solution 1 flow public class HomePresenter {
 
 public void loadPost(final int postId){
 mApi.getPostById(postId, new Callback() {
 public void success(Post post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 37

Slide 37 text

Solution 1 flow public class HomeActivity extends AppCompatActivity implements IHomeView {
 @Override
 public void onPostLoaded(Post post) {
 Toast.makeText(HomeActivity.this,post.getBody(),Toast.LENGTH_SHORT) .show();
 mPresenter.loadComments(post.getId());
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 38

Slide 38 text

Solution 1 flow public class HomePresenter {
 
 public void loadComments(int postId){
 mApi.getPostComments(postId, new Callback>() {
 @Override
 public void success(List commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 39

Slide 39 text

Solution 1 flow public class HomeActivity extends AppCompatActivity implements IHomeView {
 @Override
 public void onCommentsLoaded(List comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 } HomeActivity HomePresenter IHomeView Retrofit

Slide 40

Slide 40 text

Solution 1 review • Activities become much lighter and easier to read, since they become decoupled from the logic. • However the logic is now implemented in the presenter. • The presenter is still very coupled to the implementation of concrete data sources. • Easier to test the UI but still hard to test the logic of the application. • The domain model objects are the ones instantiated by gson (inside retrofit) where field names must match with the json response (if you don’t use annotations).

Slide 41

Slide 41 text

Reactive 
 Clean Architecture www.fernandocejas.com/2015/07/18/architecting-android-the- evolution/

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

“Wow”

Slide 44

Slide 44 text

“Android..the evolution”

Slide 45

Slide 45 text

I understand (some of) the benefits but…

Slide 46

Slide 46 text

How do I start?

Slide 47

Slide 47 text

What are the reasons for some of the implementation decisions?

Slide 48

Slide 48 text

I don’t get it…

Slide 49

Slide 49 text

Maybe later…

Slide 50

Slide 50 text

Everyone is talking about RxJava…

Slide 51

Slide 51 text

…so let’s try it!

Slide 52

Slide 52 text

RxJava “A library for composing asynchronous and event-based programs using observable sequences for the Java VM.” source: github.com/ReactiveX/RxJava

Slide 53

Slide 53 text

RxJava “The basic building blocks of reactive code are Observables and Subscribers. An Observable emits items; a Subscriber consumes those items. There is a pattern to how items are emitted. An Observable may emit any number of items (including zero items), then it terminates either by successfully completing, or due to an error. For each Subscriber it has, an Observable calls Subscriber.onNext() any number of times, followed by either Subscriber.onComplete() or Subscriber.onError(). This looks a lot like your standard observer pattern, but it differs in one key way - Observables often don't start emitting items until someone explicitly subscribes to them.” source: danlew.net

Slide 54

Slide 54 text

RxJava “The basic building blocks of reactive code are Observables and Subscribers. An Observable emits items; a Subscriber consumes those items. There is a pattern to how items are emitted. An Observable may emit any number of items (including zero items), then it terminates either by successfully completing, or due to an error. For each Subscriber it has, an Observable calls Subscriber.onNext() any number of times, followed by either Subscriber.onComplete() or Subscriber.onError(). This looks a lot like your standard observer pattern, but it differs in one key way - Observables often don't start emitting items until someone explicitly subscribes to them.” source: danlew.net

Slide 55

Slide 55 text

Retrofit without RxJava mApi.getPostById(postId, new Callback() {
 public void success(Post post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });

Slide 56

Slide 56 text

Retrofit with RxJava Instead of adding a Callback as a parameter:
 @GET("/posts/{postId}")
 void getPostById(@Path("postId") int postId, Callback callback); we change the return type to Observable
 @GET("/posts/{postId}")
 Observable getPostById(@Path("postId") int postId);

Slide 57

Slide 57 text

Retrofit with RxJava @GET("/posts/{postId}")
 Observable getPostById(@Path("postId") int postId); mApi.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });

Slide 58

Slide 58 text

Retrofit with RxJava @GET("/posts/{postId}")
 Observable getPostById(@Path("postId") int postId); mApi.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });

Slide 59

Slide 59 text

Retrofit with RxJava @GET("/posts/{postId}")
 Observable getPostById(@Path("postId") int postId); mApi.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });

Slide 60

Slide 60 text

Retrofit with RxJava @GET("/posts/{postId}")
 Observable getPostById(@Path("postId") int postId); mApi.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });

Slide 61

Slide 61 text

Retrofit with RxJava @GET("/posts/{postId}")
 Observable getPostById(@Path("postId") int postId); mApi.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });

Slide 62

Slide 62 text

RxJava niceties 1. Helps a lot with its threading API.

Slide 63

Slide 63 text

RxJava niceties 1. Helps a lot with its threading API. • RxAndroid adds a specific scheduler for the UI thread;

Slide 64

Slide 64 text

RxJava Operators

Slide 65

Slide 65 text

RxJava transformations mApi.getPostById(postId)
 .map(new Func1() {
 @Override
 public Foo call(Post post) {
 return new Foo();
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Foo foo) {
 mView.onFooLoaded(foo);
 }
 });

Slide 66

Slide 66 text

RxJava transformations mApi.getPostById(postId)
 .map(new Func1() {
 @Override
 public Foo call(Post post) {
 return new Foo();
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Foo foo) {
 mView.onFooLoaded(foo);
 }
 });

Slide 67

Slide 67 text

RxJava transformations mApi.getPostById(postId)
 .map(new Func1() {
 @Override
 public Foo call(Post post) {
 return new Foo();
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Foo foo) {
 mView.onFooLoaded(foo);
 }
 });

Slide 68

Slide 68 text

RxJava transformations mApi.getPostById(postId)
 .map(new Func1() {
 @Override
 public Foo call(Post post) {
 return new Foo();
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Foo foo) {
 mView.onFooLoaded(foo);
 }
 });

Slide 69

Slide 69 text

RxJava Concatenations

Slide 70

Slide 70 text

RxJava concatenations mApi.getPostById(postId)
 .concatMap(new Func1>>() {
 @Override
 public Observable> call(Post post) {
 return mApi.getCommentsByPostId(post.getId());
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {}
 
 @Override
 public void onNext(List comments) {
 }
 }); Notice how in the same chain, we are performing 2 HTTP requests.

Slide 71

Slide 71 text

RxJava concatenations mApi.getPostById(postId)
 .concatMap(new Func1>>() {
 @Override
 public Observable> call(Post post) {
 return mApi.getCommentsByPostId(post.getId());
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {}
 
 @Override
 public void onNext(List comments) {
 }
 }); Notice how in the same chain, we are performing 2 HTTP requests.

Slide 72

Slide 72 text

RxJava concatenations mApi.getPostById(postId)
 .concatMap(new Func1>>() {
 @Override
 public Observable> call(Post post) {
 return mApi.getCommentsByPostId(post.getId());
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {}
 
 @Override
 public void onNext(List comments) {
 }
 }); Notice how in the same chain, we are performing 2 HTTP requests.

Slide 73

Slide 73 text

RxJava concatenations mApi.getPostById(postId)
 .concatMap(new Func1>>() {
 @Override
 public Observable> call(Post post) {
 return mApi.getCommentsByPostId(post.getId());
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {}
 
 @Override
 public void onNext(List comments) {
 }
 }); Notice how in the same chain, we are performing 2 HTTP requests.

Slide 74

Slide 74 text

RxJava concatenations mApi.getPostById(postId)
 .concatMap(new Func1>>() {
 @Override
 public Observable> call(Post post) {
 return mApi.getCommentsByPostId(post.getId());
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {}
 
 @Override
 public void onNext(List comments) {
 }
 }); Notice how in the same chain, we are performing 2 HTTP requests.

Slide 75

Slide 75 text

RxJava niceties 1. Helps a lot with its threading API. • RxAndroid adds a specific scheduler for the UI thread; 2. Contains operators to transform, filter and convert multiple sets of data;

Slide 76

Slide 76 text

RxJava niceties 1. Helps a lot with its threading API. • RxAndroid adds a specific scheduler for the UI thread; 2. Contains operators to transform, filter and convert multiple sets of data; 3. Handles errors in a clean and organised way;

Slide 77

Slide 77 text

RxJava Besides transformations and concatenations, RxJava provides many other useful operators. For a more detailed explanation about all the Rx operators, please check http://rxmarbles.com/ as well as all the other websites in the resources of this presentation.

Slide 78

Slide 78 text

Solution 2 https://speakerdeck.com/rallat/android-development-like-a-pro 
 (similar MVP architecture)

Slide 79

Slide 79 text

MVP architecture 2 HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources domain
 model Retrofit app SQLite IPostsModel IPostsModel

Slide 80

Slide 80 text

Solution 2 flow • The flow is the same as the solution 1 with a small but important difference. The data sources are now accessed through an interface. • Each presenter now becomes abstracted of the concrete data sources. • The model (of MVP) is an object that contains the business logic and data sources. • RxJava was used in the presenters and in the models.

Slide 81

Slide 81 text

Solution 2 flow public class HomePresenter {
 
 private IHomeView mView;
 private IPostsModel mRemote, mLocal;
 
 public HomePresenter(IHomeView view) {
 mRemote = new PostRetrofitModel();
 mLocal = new PostInMemoryModel();
 mView = view;
 }
 
 public void loadPost(final int postId){
 mRemote.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onCompleted() { 
 }
 
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getLocalizedMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });
 } HomeActivity HomePresenter IHomeView Retrofit SQLite IPostsModel IPostsModel

Slide 82

Slide 82 text

Solution 2 flow public class PostRetrofitModel implements IPostsModel {
 
 private PostApi mApi;
 
 public PostRetrofitModel() {
 mApi = ApiProvider.getApi().create(PostApi.class);
 }
 
 @Override
 public Observable getPostById(int id) {
 return mApi.getPostById(id);
 }
 
 @Override
 public Observable> getCommentsByPostId(int id) {
 return mApi.getPostComments(id);
 }
 } HomeActivity HomePresenter IHomeView Retrofit SQLite IPostsModel IPostsModel

Slide 83

Slide 83 text

Solution 2 review • Activities become much lighter and easier to read, since they become decoupled from the logic (just like solution 1). • The presenter is decoupled from the implementation of concrete data sources but still contains fair amounts of application logic. • Easier to test the UI and the data sources. • Still hard to test the logic of the application. Logic may be repeated across presenters.

Slide 84

Slide 84 text

Some weeks later..

Slide 85

Slide 85 text

MVP architecture • “First thing to clarify is that MVP is not an architectural pattern, it’s only responsible for the presentation layer.” - Antonio Leiva • “You want to separate business logic from user interface (UI) logic to make the code easier to understand and maintain.” - MSDN, MVP objectives • Martin fowler has a blog post only about GUI architectures - 
 http://martinfowler.com/eaaDev/uiArchs.html

Slide 86

Slide 86 text

Hum…

Slide 87

Slide 87 text

MVP has nothing to do with data storage..

Slide 88

Slide 88 text

…or business logic…

Slide 89

Slide 89 text

…it’s only about the UI!

Slide 90

Slide 90 text

Reactive 
 Clean Architecture www.fernandocejas.com/2015/07/18/architecting-android-the- evolution/

Slide 91

Slide 91 text

Solution 3

Slide 92

Slide 92 text

Clean Architecture • Independent of frameworks • Testable. • Independent of UI. • Independent of Database. • Independent of any external agency.

Slide 93

Slide 93 text

Business Logic
 User Interface
 Data Sources app domain data Business Logic Data Sources User Interface

Slide 94

Slide 94 text

HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources view
 model app domain data Business Logic Data Sources

Slide 95

Slide 95 text

HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources view
 model app data Data Sources PostService domain
 model domain IPostRepository

Slide 96

Slide 96 text

HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources view
 model app Retrofit SQLite remote
 model data local
 model PostLocalRepository PostRemoteRepository PostRepository PostService domain
 model domain IPostRepository

Slide 97

Slide 97 text

HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources view
 model Retrofit app SQLite remote
 model data local
 model PostLocalRepository PostRemoteRepository PostRepository domain
 model domain PostService IPostRepository

Slide 98

Slide 98 text

HomeActivity HomePresenter IHomeView view
 model Retrofit app SQLite remote
 model data local
 model Android
 Phone
 Module Java 
 Library Android 
 Library PostLocalRepository PostRemoteRepository PostRepository PostService domain
 model domain IPostRepository

Slide 99

Slide 99 text

HomeActivity HomePresenter IHomeView view
 model Retrofit app SQLite remote
 model data local
 model Android
 Phone
 Module Java 
 Library Android 
 Library Module 
 Dependencies PostLocalRepository PostRemoteRepository PostRepository PostService domain
 model domain IPostRepository

Slide 100

Slide 100 text

HomeActivity HomePresenter IHomeView view
 model Retrofit app SQLite remote
 model data local
 model Android
 Phone
 Module Java 
 Library Android 
 Library JUnit
 Mockito JUnit Espresso Testing PostLocalRepository PostRemoteRepository PostRepository PostService domain
 model domain IPostRepository

Slide 101

Slide 101 text

Solution 3 flow public class HomeActivity extends AppCompatActivity implements IHomeView {
 
 @Override
 protected void onStart() {
 super.onStart();
 mPresenter.loadPost(1);
 }
 HomeActivity HomePresenter IHomeView Retrofit PostService PostRepository PostRemoteRepository

Slide 102

Slide 102 text

Solution 3 flow public class HomePresenter implements IHomePresenter{
 
 private IHomeView mView;
 private IPostService mService;
 private IMapper mPostMapper;
 private IMapper mCommentMapper;
 
 
 public void loadPost(final int postId){
 mService.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .map(new Func1() {
 @Override
 public PostView call(Post post) {
 return mPostMapper.transform(post);
 }
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber() {
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getLocalizedMessage());
 }
 
 @Override
 public void onNext(PostView postView) {
 mView.onPostLoaded(postView);
 }
 });
 } HomeActivity HomePresenter IHomeView Retrofit PostService PostRepository PostRemoteRepository

Slide 103

Slide 103 text

Solution 3 flow public class PostService implements IPostService{
 
 IPostRepository mRepository;
 
 public PostService(IPostRepository mRepository) {
 this.mRepository = mRepository;
 }
 
 @Override
 public Observable getPostById(int postId) {
 return mRepository.getPostById(postId);
 }
 
 @Override
 public Observable> getPostComments(int postId) {
 return mRepository.getPostComments(postId);
 }
 }
 Each service could use different repositories, if needed. HomeActivity HomePresenter IHomeView Retrofit PostService PostRepository PostRemoteRepository

Slide 104

Slide 104 text

Solution 3 flow public class PostRepository implements IPostRepository {
 
 IPostRepository mRemote; //Ex. retrofit
 IPostRepository mLocal; //Ex. sqlite
 
 public PostRepository(IPostRepository mRemote, IPostRepository mLocal) {
 this.mRemote = mRemote;
 this.mLocal = mLocal;
 }
 
 @Override
 public Observable getPostById(int postId) {
 return mRemote.getPostById(postId);
 }
 
 @Override
 public Observable> getPostComments(int postId) {
 return mRemote.getPostComments(postId);
 }
 }
 Each repository should be able to sync data between the local and the remote databases. For demonstration purposes, we will use the remote database only. HomeActivity HomePresenter IHomeView Retrofit PostService PostRepository PostRemoteRepository

Slide 105

Slide 105 text

Solution 3 flow public class PostRepositoryRetrofit implements IPostRepository {
 
 private PostApi mApi;
 private IMapper mPostMapper;
 private IMapper mCommentMapper;
 
 public PostRepositoryRetrofit(IMapper postMapper, 
 IMapper commentMapper) {
 mApi = ApiProvider.getApi().create(PostApi.class);
 mPostMapper = postMapper;
 mCommentMapper = commentMapper;
 }
 
 @Override
 public Observable getPostById(int postId) {
 return mApi.getPostById(postId)
 .map(new Func1() {
 @Override
 public Post call(PostJson postJson) {
 return mPostMapper.transform(postJson);
 }
 });
 } HomeActivity HomePresenter IHomeView Retrofit PostService PostRemoteRepository PostRepository

Slide 106

Slide 106 text

Clean architecture • Every layer is decoupled. • Easier to test the data sources, the UI and each domain service. • There is a concrete object model for each layer. • Great for implementing dependency injection mechanisms (ex: dagger) • Easier to scale and maintain. • Implementing new features can take longer than the other solutions but it isn’t more complex.

Slide 107

Slide 107 text

However

Slide 108

Slide 108 text

Take-away • Design and implement the architecture the way it fits your needs and the problem you are trying to solve. • Different apps have different needs. • Some could use several different api’s, others may not need a local database, etc. • The syncing algorithm (online vs offline) also varies a lot between companies and products. • There is no silver bullet. • It’s always about trade-offs.

Slide 109

Slide 109 text

Thank you Repositories available (Mono, V1, V2, V3) on my Github
 fmendes6.com
 @fmendes6 PS: Yes, it has Hungarian notation :(

Slide 110

Slide 110 text

References • https://github.com/android10/Android-CleanArchitecture • http://hannesdorfmann.com/android/plaid-refactored-2/ • http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/ 
 (Grokking rxjava collection) • https://speakerdeck.com/rallat/how-to-survive-a-legacy-code-apocalypse-on-android • http://matthewwear.xyz/playing-with-mvp-suggestions/ • https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html • Ingredients for a Healthy Codebase - Roman Piel - Songkick • http://martinfowler.com/eaaDev/uiArchs.html