Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Mono to Clean

From Mono to Clean

My journey on how I evolved from a monolithic architecture to a Clean Architecture with RxJava based on Fernando Cejas Proposal.

Filipe Mendes

August 16, 2016
Tweet

More Decks by Filipe Mendes

Other Decks in Programming

Transcript

  1. From mono to clean
    @fmendes6

    View Slide

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

    View Slide

  3. 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.

    View Slide

  4. 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.

    View Slide

  5. Let’s get started.

    View Slide

  6. 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…”

    }

    View Slide

  7. 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.

    View Slide

  8. 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.

    View Slide

  9. retrofit
    Monolithic architecture
    HomeActivity
    Business Logic

    User Interface

    Data Sources
    Retrofit
    domain

    model
    app

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

  12. 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

    View Slide

  13. 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

    View Slide

  14. 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

    View Slide

  15. 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.

    View Slide

  16. Solution 1
    Model View Presenter

    View Slide

  17. MVP architecture
    HomeActivity
    HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    domain

    model
    Retrofit
    app

    View Slide

  18. Model

    View Slide

  19. 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

    View Slide

  20. View

    View Slide

  21. View
    public interface IMainView {


    void onPostLoaded(Post post);


    void onCommentsLoaded(List comments);


    void onError(String error);

    }

    HomeActivity
    HomePresenter
    IHomeView
    Retrofit

    View Slide

  22. Activity

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

  25. 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

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

  29. Presenter

    View Slide

  30. 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

    View Slide

  31. 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

    View Slide

  32. 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

    View Slide

  33. 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

    View Slide

  34. Solution architecture
    HomeActivity
    HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    domain

    model
    Retrofit
    app

    View Slide

  35. Solution 1 flow
    public class HomeActivity extends AppCompatActivity implements IHomeView {


    @Override

    protected void onStart() {

    super.onStart();

    mPresenter.loadPost(1);

    }
    HomeActivity
    HomePresenter
    IHomeView
    Retrofit

    View Slide

  36. 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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

  40. 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).

    View Slide

  41. Reactive 

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

    View Slide

  42. View Slide

  43. “Wow”

    View Slide

  44. “Android..the evolution”

    View Slide

  45. I understand (some of)
    the benefits but…

    View Slide

  46. How do I start?

    View Slide

  47. What are the reasons for
    some of the
    implementation decisions?

    View Slide

  48. I don’t get it…

    View Slide

  49. Maybe later…

    View Slide

  50. Everyone is talking
    about RxJava…

    View Slide

  51. …so let’s try it!

    View Slide

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

    View Slide

  53. 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

    View Slide

  54. 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

    View Slide

  55. 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());

    }

    });

    View Slide

  56. 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);

    View Slide

  57. 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);

    }

    });

    View Slide

  58. 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);

    }

    });

    View Slide

  59. 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);

    }

    });

    View Slide

  60. 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);

    }

    });

    View Slide

  61. 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);

    }

    });

    View Slide

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

    View Slide

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

    View Slide

  64. RxJava Operators

    View Slide

  65. 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);

    }

    });

    View Slide

  66. 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);

    }

    });

    View Slide

  67. 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);

    }

    });

    View Slide

  68. 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);

    }

    });

    View Slide

  69. RxJava
    Concatenations

    View Slide

  70. 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.

    View Slide

  71. 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.

    View Slide

  72. 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.

    View Slide

  73. 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.

    View Slide

  74. 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.

    View Slide

  75. 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;

    View Slide

  76. 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;

    View Slide

  77. 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.

    View Slide

  78. Solution 2
    https://speakerdeck.com/rallat/android-development-like-a-pro 

    (similar MVP architecture)

    View Slide

  79. MVP architecture 2
    HomeActivity
    HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    domain

    model
    Retrofit
    app
    SQLite
    IPostsModel IPostsModel

    View Slide

  80. 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.

    View Slide

  81. 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

    View Slide

  82. 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

    View Slide

  83. 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.

    View Slide

  84. Some weeks later..

    View Slide

  85. 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

    View Slide

  86. Hum…

    View Slide

  87. MVP has nothing to do
    with data storage..

    View Slide

  88. …or business logic…

    View Slide

  89. …it’s only about the UI!

    View Slide

  90. Reactive 

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

    View Slide

  91. Solution 3

    View Slide

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

    View Slide

  93. Business Logic

    User Interface

    Data Sources
    app
    domain
    data
    Business Logic
    Data Sources
    User Interface

    View Slide

  94. HomeActivity HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    view

    model
    app
    domain
    data
    Business Logic
    Data Sources

    View Slide

  95. HomeActivity HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    view

    model
    app
    data
    Data Sources
    PostService
    domain

    model
    domain
    IPostRepository

    View Slide

  96. 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

    View Slide

  97. 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

    View Slide

  98. 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

    View Slide

  99. 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

    View Slide

  100. 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

    View Slide

  101. 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

    View Slide

  102. 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

    View Slide

  103. 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

    View Slide

  104. 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

    View Slide

  105. 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

    View Slide

  106. 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.

    View Slide

  107. However

    View Slide

  108. 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.

    View Slide

  109. Thank you
    Repositories available (Mono, V1, V2, V3) on my Github

    fmendes6.com

    @fmendes6
    PS: Yes, it has Hungarian notation :(

    View Slide

  110. 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

    View Slide