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.

E24250ad371aa55fbaa329e0d01f60a1?s=128

Filipe Mendes

August 16, 2016
Tweet

Transcript

  1. From mono to clean @fmendes6

  2. Filipe Mendes • Android Developer • GDG Lisbon Organizer •

    Sharednode.org • @fmendes6 • www.fmendes6.com
  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.
  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.
  5. Let’s get started.

  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…”
 }
  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.
  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.
  9. retrofit Monolithic architecture HomeActivity Business Logic
 User Interface
 Data Sources

    Retrofit domain
 model app
  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<Post>() {
 @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<List<Comment>>() {
 @Override
 public void success(List<Comment> 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
  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<Post>() {
 @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<List<Comment>>() {
 @Override
 public void success(List<Comment> 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
  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<Post>() {
 @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<List<Comment>>() {
 @Override
 public void success(List<Comment> 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
  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<Post>() {
 @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<List<Comment>>() {
 @Override
 public void success(List<Comment> 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
  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<Post>() {
 @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<List<Comment>>() {
 @Override
 public void success(List<Comment> 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
  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.
  16. Solution 1 Model View Presenter

  17. MVP architecture HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data

    Sources domain
 model Retrofit app
  18. Model

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

  21. View public interface IMainView {
 
 void onPostLoaded(Post post);
 


    void onCommentsLoaded(List<Comment> comments);
 
 void onError(String error);
 }
 HomeActivity HomePresenter IHomeView Retrofit
  22. Activity

  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<Comment> 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
  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<Comment> 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
  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<Comment> 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
  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<Comment> 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
  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<Comment> 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
  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<Comment> 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
  29. Presenter

  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<Post>() {
 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<List<Comment>>() {
 @Override
 public void success(List<Comment> commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter
  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<Post>() {
 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<List<Comment>>() {
 @Override
 public void success(List<Comment> commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter
  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<Post>() {
 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<List<Comment>>() {
 @Override
 public void success(List<Comment> commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter
  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<Post>() {
 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<List<Comment>>() {
 @Override
 public void success(List<Comment> commentList, Response response) {
 mView.onCommentsLoaded(commentList);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
 }
 } HomeActivity HomePresenter IHomeView Retrofit Presenter
  34. Solution architecture HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data

    Sources domain
 model Retrofit app
  35. Solution 1 flow public class HomeActivity extends AppCompatActivity implements IHomeView

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

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

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

    {
 @Override
 public void onCommentsLoaded(List<Comment> comments) {
 Toast.makeText(HomeActivity.this,"total comments="+comments.size(),Toast.LENGTH_SHORT).show();
 } HomeActivity HomePresenter IHomeView Retrofit
  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).
  41. Reactive 
 Clean Architecture www.fernandocejas.com/2015/07/18/architecting-android-the- evolution/

  42. None
  43. “Wow”

  44. “Android..the evolution”

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

  46. How do I start?

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

  48. I don’t get it…

  49. Maybe later…

  50. Everyone is talking about RxJava…

  51. …so let’s try it!

  52. RxJava “A library for composing asynchronous and event-based programs using

    observable sequences for the Java VM.” source: github.com/ReactiveX/RxJava
  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
  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
  55. Retrofit without RxJava mApi.getPostById(postId, new Callback<Post>() {
 public void success(Post

    post, Response response) {
 mView.onPostLoaded(post);
 }
 
 @Override
 public void failure(RetrofitError error) {
 mView.onError(error.getMessage());
 }
 });
  56. Retrofit with RxJava Instead of adding a Callback<Post> as a

    parameter:
 @GET("/posts/{postId}")
 void getPostById(@Path("postId") int postId, Callback<Post> callback); we change the return type to Observable<Post>
 @GET("/posts/{postId}")
 Observable<Post> getPostById(@Path("postId") int postId);
  57. Retrofit with RxJava @GET("/posts/{postId}")
 Observable<Post> getPostById(@Path("postId") int postId); mApi.getPostById(postId)
 .subscribeOn(Schedulers.io())


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


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


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


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


    .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber<Post>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Post post) {
 mView.onPostLoaded(post);
 }
 });
  62. RxJava niceties 1. Helps a lot with its threading API.

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

    • RxAndroid adds a specific scheduler for the UI thread;
  64. RxJava Operators

  65. RxJava transformations mApi.getPostById(postId)
 .map(new Func1<Post, Foo>() {
 @Override
 public Foo

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

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

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

    call(Post post) {
 return new Foo();
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber<Foo>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {
 mView.onError(e.getMessage());
 }
 
 @Override
 public void onNext(Foo foo) {
 mView.onFooLoaded(foo);
 }
 });
  69. RxJava Concatenations

  70. RxJava concatenations mApi.getPostById(postId)
 .concatMap(new Func1<Post, Observable<List<Comment>>>() {
 @Override
 public Observable<List<Comment>>

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

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

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

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

    call(Post post) {
 return mApi.getCommentsByPostId(post.getId());
 }
 })
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber<List<Comment>>() {
 @Override
 public void onCompleted() {}
 @Override
 public void onError(Throwable e) {}
 
 @Override
 public void onNext(List<Comment> comments) {
 }
 }); Notice how in the same chain, we are performing 2 HTTP requests.
  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;
  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;
  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.
  78. Solution 2 https://speakerdeck.com/rallat/android-development-like-a-pro 
 (similar MVP architecture)

  79. MVP architecture 2 HomeActivity HomePresenter IHomeView Business Logic
 User Interface


    Data Sources domain
 model Retrofit app SQLite IPostsModel IPostsModel
  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.
  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<Post>() {
 @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
  82. Solution 2 flow public class PostRetrofitModel implements IPostsModel {
 


    private PostApi mApi;
 
 public PostRetrofitModel() {
 mApi = ApiProvider.getApi().create(PostApi.class);
 }
 
 @Override
 public Observable<Post> getPostById(int id) {
 return mApi.getPostById(id);
 }
 
 @Override
 public Observable<List<Comment>> getCommentsByPostId(int id) {
 return mApi.getPostComments(id);
 }
 } HomeActivity HomePresenter IHomeView Retrofit SQLite IPostsModel IPostsModel
  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.
  84. Some weeks later..

  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
  86. Hum…

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

  88. …or business logic…

  89. …it’s only about the UI!

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

  91. Solution 3

  92. Clean Architecture • Independent of frameworks • Testable. • Independent

    of UI. • Independent of Database. • Independent of any external agency.
  93. Business Logic
 User Interface
 Data Sources app domain data Business

    Logic Data Sources User Interface
  94. HomeActivity HomePresenter IHomeView Business Logic
 User Interface
 Data Sources view


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


    model app data Data Sources PostService domain
 model domain IPostRepository
  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
  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
  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
  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
  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
  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
  102. Solution 3 flow public class HomePresenter implements IHomePresenter{
 
 private

    IHomeView mView;
 private IPostService mService;
 private IMapper<Post,PostView> mPostMapper;
 private IMapper<Comment,CommentView> mCommentMapper;
 
 
 public void loadPost(final int postId){
 mService.getPostById(postId)
 .subscribeOn(Schedulers.io())
 .map(new Func1<Post, PostView>() {
 @Override
 public PostView call(Post post) {
 return mPostMapper.transform(post);
 }
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Subscriber<PostView>() {
 @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
  103. Solution 3 flow public class PostService implements IPostService{
 
 IPostRepository

    mRepository;
 
 public PostService(IPostRepository mRepository) {
 this.mRepository = mRepository;
 }
 
 @Override
 public Observable<Post> getPostById(int postId) {
 return mRepository.getPostById(postId);
 }
 
 @Override
 public Observable<List<Comment>> getPostComments(int postId) {
 return mRepository.getPostComments(postId);
 }
 }
 Each service could use different repositories, if needed. HomeActivity HomePresenter IHomeView Retrofit PostService PostRepository PostRemoteRepository
  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<Post> getPostById(int postId) {
 return mRemote.getPostById(postId);
 }
 
 @Override
 public Observable<List<Comment>> 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
  105. Solution 3 flow public class PostRepositoryRetrofit implements IPostRepository {
 


    private PostApi mApi;
 private IMapper<PostJson,Post> mPostMapper;
 private IMapper<CommentJson, Comment> mCommentMapper;
 
 public PostRepositoryRetrofit(IMapper<PostJson, Post> postMapper, 
 IMapper<CommentJson, Comment> commentMapper) {
 mApi = ApiProvider.getApi().create(PostApi.class);
 mPostMapper = postMapper;
 mCommentMapper = commentMapper;
 }
 
 @Override
 public Observable<Post> getPostById(int postId) {
 return mApi.getPostById(postId)
 .map(new Func1<PostJson, Post>() {
 @Override
 public Post call(PostJson postJson) {
 return mPostMapper.transform(postJson);
 }
 });
 } HomeActivity HomePresenter IHomeView Retrofit PostService PostRemoteRepository PostRepository
  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.
  107. However

  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.
  109. Thank you Repositories available (Mono, V1, V2, V3) on my

    Github
 fmendes6.com
 @fmendes6 PS: Yes, it has Hungarian notation :(
  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