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 full-size slide

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

    View full-size 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 full-size 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 full-size slide

  5. Let’s get started.

    View full-size 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 full-size 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 full-size 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 full-size slide

  9. retrofit
    Monolithic architecture
    HomeActivity
    Business Logic

    User Interface

    Data Sources
    Retrofit
    domain

    model
    app

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

  16. Solution 1
    Model View Presenter

    View full-size slide

  17. MVP architecture
    HomeActivity
    HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    domain

    model
    Retrofit
    app

    View full-size slide

  18. 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 full-size slide

  19. View
    public interface IMainView {


    void onPostLoaded(Post post);


    void onCommentsLoaded(List comments);


    void onError(String error);

    }

    HomeActivity
    HomePresenter
    IHomeView
    Retrofit

    View full-size slide

  20. 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 full-size slide

  21. 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 full-size slide

  22. 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 full-size slide

  23. 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 full-size 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 full-size 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 full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. 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 full-size slide

  29. 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 full-size slide

  30. Solution architecture
    HomeActivity
    HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    domain

    model
    Retrofit
    app

    View full-size slide

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


    @Override

    protected void onStart() {

    super.onStart();

    mPresenter.loadPost(1);

    }
    HomeActivity
    HomePresenter
    IHomeView
    Retrofit

    View full-size slide

  32. 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 full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. Reactive 

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

    View full-size slide

  38. “Android..the evolution”

    View full-size slide

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

    View full-size slide

  40. How do I start?

    View full-size slide

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

    View full-size slide

  42. I don’t get it…

    View full-size slide

  43. Maybe later…

    View full-size slide

  44. Everyone is talking
    about RxJava…

    View full-size slide

  45. …so let’s try it!

    View full-size slide

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

    View full-size slide

  47. 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 full-size slide

  48. 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 full-size slide

  49. 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 full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

  55. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  58. RxJava Operators

    View full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

  61. 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 full-size slide

  62. 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 full-size slide

  63. RxJava
    Concatenations

    View full-size slide

  64. 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 full-size slide

  65. 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 full-size slide

  66. 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 full-size slide

  67. 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 full-size slide

  68. 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 full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

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

    (similar MVP architecture)

    View full-size slide

  73. MVP architecture 2
    HomeActivity
    HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    domain

    model
    Retrofit
    app
    SQLite
    IPostsModel IPostsModel

    View full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

  76. 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 full-size slide

  77. 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 full-size slide

  78. Some weeks later..

    View full-size slide

  79. 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 full-size slide

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

    View full-size slide

  81. …or business logic…

    View full-size slide

  82. …it’s only about the UI!

    View full-size slide

  83. Reactive 

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

    View full-size slide

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

    View full-size slide

  85. Business Logic

    User Interface

    Data Sources
    app
    domain
    data
    Business Logic
    Data Sources
    User Interface

    View full-size slide

  86. HomeActivity HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    view

    model
    app
    domain
    data
    Business Logic
    Data Sources

    View full-size slide

  87. HomeActivity HomePresenter
    IHomeView
    Business Logic

    User Interface

    Data Sources
    view

    model
    app
    data
    Data Sources
    PostService
    domain

    model
    domain
    IPostRepository

    View full-size slide

  88. 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 full-size slide

  89. 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 full-size slide

  90. 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 full-size slide

  91. 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 full-size slide

  92. 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 full-size slide

  93. 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 full-size slide

  94. 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 full-size slide

  95. 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 full-size slide

  96. 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 full-size slide

  97. 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 full-size slide

  98. 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 full-size slide

  99. 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 full-size slide

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

    fmendes6.com

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

    View full-size slide

  101. 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 full-size slide