Android development like a pro

7ace70cd355db1983dea895fbe01a4ef?s=47 rallat
September 30, 2015

Android development like a pro

Repo: https://github.com/rallat/EffectiveAndroid

Slides from the talk http://www.meetup.com/sfandroid/events/224952292/

The talk will show how to make your app scalable, your code clean, your performance optimized and your UI neat. The talk will show in a pragmatic way the pros and cons of using certain Android APIs, strategies and libraries.

7ace70cd355db1983dea895fbe01a4ef?s=128

rallat

September 30, 2015
Tweet

Transcript

  1. Android development like a pro #AndroidDevPro September 29, 2015

  2. Israel Camacho September 29, 2015 Android Engineer at Twitter #AndroidDevPro

  3. None
  4. Fragment

  5. Fragment AsyncTask

  6. Fragment AsyncTask Loader

  7. None
  8. None
  9. None
  10. What is your goal?

  11. Reliable, Scalable and Maintainable

  12. None
  13. Deliver

  14. Deliver Really

  15. Deliver Really Awesome

  16. Deliver Really Awesome Features

  17. Deliver Really Awesome Features Fast

  18. None
  19. Reliable, Scalable and Maintainable

  20. Architecture Test

  21. None
  22. Fragments

  23. Pros Encapsulates full features (multiples views, asynctasks, loaders). Reusable between

    different Activities.
  24. Burrito design pattern

  25. None
  26. None
  27. None
  28. It doesn’t help to decouple your business logic!

  29. None
  30. None
  31. None
  32. WHY!?

  33. WHY!?

  34. Alternatives Single activity + navigation manager Many activities with reusable

    views
  35. LaunchActivity NavigationManager Uri: “”

  36. LaunchActivity NavigationManager startScreen(Screen1.class) Uri: “”

  37. LaunchActivity NavigationManager startScreen(Screen1.class) inflates(Screen1.class) Uri: “screen1”

  38. Screen1 URI: screen1 LaunchActivity NavigationManager startScreen(Screen1.class) inflates(Screen1.class) Uri: “screen1”

  39. Screen1 URI: screen1 LaunchActivity NavigationManager startScreen(Screen1.class) inflates(Screen1.class) Uri: “screen1” User

    starts screen 2
  40. Screen1 URI: screen1 LaunchActivity NavigationManager inflates(Screen1.class) startScreen(Screen2.class) Uri: “screen1” User

    starts screen 2
  41. Screen1 URI: screen1 LaunchActivity Screen2 URI: screen2 NavigationManager startScreen(Screen2.class) Uri:

    “screen1/screen2” inflates(Screen2class) User starts screen 2
  42. LET ME EXPLAIN YOU ANDROID LIKE A PRO

  43. Let’s make an app

  44. None
  45. None
  46. “Decouple your app from implementation details" - Anonymous

  47. public interface ImageLoader { void load(ImageView view, String url); void

    loadWithTransformation(ImageView view, String url, Transformation transformation); }
  48. public class PicassoImageLoader implements ImageLoader { private final Picasso picasso;

    … @Override public void load(ImageView view, String url) { picasso.load(url).into(view); } @Override public void loadWithTransformation(ImageView view, String url, Transformation transformation) { picasso.load(url).transform(transformation).into(view); } }
  49. public class GlideImageLoader implements ImageLoader { private final Glide glide;

    … @Override public void load(ImageView view, String url) { glide.load(url).into(view); } @Override public void loadWithTransformation(ImageView view, String url, Transformation transformation) { glide.load(url).transform(transformation).into(view); } }
  50. Common UI

  51. None
  52. Toolbar DrawerLayout

  53. public interface ScreenContainer { /** * The root {@link android.view.ViewGroup}

    into which the activity should place its contents. */ ViewGroup bind(AppCompatActivity activity); /** * Returns the drawerLayout of this window. * */ DrawerLayout getDrawerLayout(); }
  54. public abstract class EffectiveActivity extends AppCompatActivity { private ViewGroup mainFrame;

    private ScreenContainerImpl screenContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); screenContainer = new ScreenContainerImpl(); mainFrame = screenContainer.bind(this); getLayoutInflater().inflate(getLayout(), mainFrame); ButterKnife.bind(this); } @NonNull abstract int getLayout(); }
  55. public abstract class EffectiveActivity extends AppCompatActivity { private ViewGroup mainFrame;

    private ScreenContainerImpl screenContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); screenContainer = new ScreenContainerImpl(); mainFrame = screenContainer.bind(this); getLayoutInflater().inflate(getLayout(), mainFrame); ButterKnife.bind(this); } @NonNull abstract int getLayout(); }
  56. public abstract class EffectiveActivity extends AppCompatActivity { private ViewGroup mainFrame;

    private ScreenContainerImpl screenContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); screenContainer = new ScreenContainerImpl(); mainFrame = screenContainer.bind(this); getLayoutInflater().inflate(getLayout(), mainFrame); ButterKnife.bind(this); } @NonNull abstract int getLayout(); }
  57. public abstract class EffectiveActivity extends AppCompatActivity { private ViewGroup mainFrame;

    private ScreenContainerImpl screenContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); screenContainer = new ScreenContainerImpl(); mainFrame = screenContainer.bind(this); getLayoutInflater().inflate(getLayout(), mainFrame); ButterKnife.bind(this); } @NonNull abstract int getLayout(); }
  58. MVP Clean Architecture

  59. View Presenter Model User Interaction

  60. View Presenter Model User Interaction notify user event

  61. View Presenter Model User Interaction notify user event request data

  62. View Presenter Model User Interaction notify user event request data

    delivers entities
  63. View Presenter Model User Interaction notify user event request data

    delivers entities update UI with entities
  64. None
  65. View

  66. public interface TopImagesListView { void setImages(List<Image> images); void logout(); }

  67. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { ... @Override

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setUpView(); presenter = new TopImagesListPresenterImpl(); presenter.create(); } @Override public void setImages(List<Image> images) { adapter = new ImageRecyclerView(images); recyclerView.setAdapter(adapter); } @Override public void logout() { TwitterCore.getInstance().logOut(); finish(); startActivity(new Intent(this, LoginActivity.class)); } ... }
  68. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { ... @Override

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setUpView(); presenter = new TopImagesListPresenterImpl(); presenter.create(); } @Override public void setImages(List<Image> images) { adapter = new ImageRecyclerView(images); recyclerView.setAdapter(adapter); } @Override public void logout() { TwitterCore.getInstance().logOut(); finish(); startActivity(new Intent(this, LoginActivity.class)); } ... }
  69. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { ... @Override

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setUpView(); presenter = new TopImagesListPresenterImpl(); presenter.create(); } @Override public void setImages(List<Image> images) { adapter = new ImageRecyclerView(images); recyclerView.setAdapter(adapter); } @Override public void logout() { TwitterCore.getInstance().logOut(); finish(); startActivity(new Intent(this, LoginActivity.class)); } ... }
  70. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { ... @Override

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setUpView(); presenter = new TopImagesListPresenterImpl(); presenter.create(); } @Override public void setImages(List<Image> images) { adapter = new ImageRecyclerView(images); recyclerView.setAdapter(adapter); } @Override public void logout() { TwitterCore.getInstance().logOut(); finish(); startActivity(new Intent(this, LoginActivity.class)); } ... }
  71. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { ... @Override

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setUpView(); presenter = new TopImagesListPresenterImpl(); presenter.create(); } @Override public void setImages(List<Image> images) { adapter = new ImageRecyclerView(images); recyclerView.setAdapter(adapter); } @Override public void logout() { TwitterCore.getInstance().logOut(); finish(); startActivity(new Intent(this, LoginActivity.class)); } ... }
  72. Presenter

  73. public interface TopImagesListPresenter extends Presenter { void create(); void setView(TopImagesListView

    view); }
  74. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  75. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  76. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  77. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  78. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  79. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  80. public class TopImagesListPresenterImpl implements TopImagesListPresenter { private final ImageListModel model;

    private TopImagesListView view; private List<Image> viewArticles; … @Override public void create() { if (viewImages == null || viewImages.size() == 0) { viewImages = new ArrayList<>(); model.getMostRtImages(new WeakCallback(view, viewImages)); } else { if (view != null) { view.setImage(articles); } } } … }
  81. public class TopImagesListPresenterImpl implements TopImagesListPresenter { … private static class

    WeakCallback extends Callback<List<Image>> { private WeakReference<TopImagesListView> view; private List<Image> imagesList; … @Override public void success(Result<List<Image>> result) { if (view.get() != null) { imagesList.addAll(result.data); view.get().setImage(result.data); } } @Override public void failure(TwitterException e) { if (view.get() != null) { view.get().logout(); } } } … }
  82. public class TopImagesListPresenterImpl implements TopImagesListPresenter { … private static class

    WeakCallback extends Callback<List<Image>> { private WeakReference<TopImagesListView> view; private List<Image> imagesList; … @Override public void success(Result<List<Image>> result) { if (view.get() != null) { imagesList.addAll(result.data); view.get().setImage(result.data); } } @Override public void failure(TwitterException e) { if (view.get() != null) { view.get().logout(); } } } … }
  83. public class TopImagesListPresenterImpl implements TopImagesListPresenter { … private static class

    WeakCallback extends Callback<List<Image>> { private WeakReference<TopImagesListView> view; private List<Image> imagesList; … @Override public void success(Result<List<Image>> result) { if (view.get() != null) { imagesList.addAll(result.data); view.get().setImage(result.data); } } @Override public void failure(TwitterException e) { if (view.get() != null) { view.get().logout(); } } } … }
  84. Model

  85. public interface TopImageListModel { void getMostRtImages(Callback<List<Image>> articles); }

  86. public class TopImageListModelImpl implements TopImageListModel { private final CustomApiClient client;

    ... @Override public void getMostRtImages(final Callback<List<Image>> callback) { client.getTimelineService().homeTimeline(200, true, true, true, true, new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Image> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  87. public class TopImageListModelImpl implements TopImageListModel { private final CustomApiClient client;

    ... @Override public void getMostRtImages(final Callback<List<Image>> callback) { client.getTimelineService().homeTimeline(200, true, true, true, true, new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Image> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  88. public class TopImageListModelImpl implements TopImageListModel { private final CustomApiClient client;

    ... @Override public void getMostRtImages(final Callback<List<Image>> callback) { client.getTimelineService().homeTimeline(200, true, true, true, true, new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Image> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  89. public class TopImageListModelImpl implements TopImageListModel { private final CustomApiClient client;

    ... @Override public void getMostRtImages(final Callback<List<Image>> callback) { client.getTimelineService().homeTimeline(200, true, true, true, true, new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Image> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  90. public class TopImageListModelImpl implements TopImageListModel { private final CustomApiClient client;

    ... @Override public void getMostRtImages(final Callback<List<Image>> callback) { client.getTimelineService().homeTimeline(200, true, true, true, true, new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Image> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  91. Test

  92. View: Test render logic and interaction with presenter, mock Presenter.

    Presenter: Test that view events invoke the right model method. mock both View and Model. Model: Test the business logic, mock the data source and Presenter.
  93. MVP Pros Increases separation of concerns in 3 layers:

  94. MVP Pros Increases separation of concerns in 3 layers: Presenter

    - Handle User events
  95. MVP Pros Increases separation of concerns in 3 layers: Passive

    View - Render logic Presenter - Handle User events
  96. MVP Pros Increases separation of concerns in 3 layers: Passive

    View - Render logic Presenter - Handle User events Model - Business logic
  97. MVP Cons

  98. MVP Cons BoilerPlate to wire the layers.

  99. MVP Cons BoilerPlate to wire the layers. Model can’t be

    reused since is too tied to the specific use case.
  100. MVP Cons BoilerPlate to wire the layers. Model can’t be

    reused since is too tied to the specific use case. View and Presenter are tied to data objects since they share the same type of object with the Model.
  101. Orientation Change

  102. PresenterHolder OnDestroy OnCreate Activity save presenter restore presenter onSavedInstanceState remove

    presenter if Activity is finishing then Running
  103. public class EffectiveAndroidApplication extends Application { Map<Class, Presenter> presenterHolder; …

    public void putPresenter(Class c, Presenter p) { presenterHolder.put(c, p); } public <T extends Presenter> T getPresenter(Class<T> c) { return (T) presenterHolder.get(c); } public void remove(Class c) { presenterMap.remove(c); } }
  104. public class PresenterHolder { static volatile PresenterHolder singleton = null;

    private Map<Class, Presenter> presenterMap; … public void putPresenter(Class c, Presenter p) { singleton.put(c, p); } public <T extends Presenter> T getPresenter(Class<T> c) { return (T) singleton.get(c); } public void remove(Class c) { presenterMap.remove(c); } }
  105. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { … @Override

    protected void onCreate(Bundle savedInstanceState) { presenter = getPresenterHolder().getPresenter(TopImagesListActivity.class); presenter.create(); } @Override protected void onSaveInstanceState(Bundle bundle) { getPresenterHolder().putPresenter(TopImagesListActivity.class, presenter); } @Override protected void onDestroy() { super.onDestroy(); presenter.setView(null); if (isFinishing()) { getPresenterHolder().remove(TopImagesListActivity.class); } } }
  106. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { … @Override

    protected void onCreate(Bundle savedInstanceState) { presenter = getPresenterHolder().getPresenter(TopImagesListActivity.class); presenter.create(); } @Override protected void onSaveInstanceState(Bundle bundle) { getPresenterHolder().putPresenter(TopImagesListActivity.class, presenter); } @Override protected void onDestroy() { super.onDestroy(); presenter.setView(null); if (isFinishing()) { getPresenterHolder().remove(TopImagesListActivity.class); } } }
  107. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { … @Override

    protected void onCreate(Bundle savedInstanceState) { presenter = getPresenterHolder().getPresenter(TopImagesListActivity.class); presenter.create(); } @Override protected void onSaveInstanceState(Bundle bundle) { getPresenterHolder().putPresenter(TopImagesListActivity.class, presenter); } @Override protected void onDestroy() { super.onDestroy(); presenter.setView(null); if (isFinishing()) { getPresenterHolder().remove(TopImagesListActivity.class); } } }
  108. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { … @Override

    protected void onCreate(Bundle savedInstanceState) { presenter = getPresenterHolder().getPresenter(TopImagesListActivity.class); presenter.create(); } @Override protected void onSaveInstanceState(Bundle bundle) { getPresenterHolder().putPresenter(TopImagesListActivity.class, presenter); } @Override protected void onDestroy() { super.onDestroy(); presenter.setView(null); if (isFinishing()) { getPresenterHolder().remove(TopImagesListActivity.class); } } }
  109. public class TopImagesListActivity extends EffectiveActivity implements TopImagesListView { … @Override

    protected void onCreate(Bundle savedInstanceState) { presenter = getPresenterHolder().getPresenter(TopImagesListActivity.class); presenter.create(); } @Override protected void onSaveInstanceState(Bundle bundle) { getPresenterHolder().putPresenter(TopImagesListActivity.class, presenter); } @Override protected void onDestroy() { super.onDestroy(); presenter.setView(null); if (isFinishing()) { getPresenterHolder().remove(TopImagesListActivity.class); } } }
  110. Clean Architecture MVP

  111. “The clean architecture” - Uncle Bob

  112. View Presenter Repository Presentation Layer Domain Layer Interactor Entity Entity

    Entity Interactor Interactor Data Layer ViewEntity User Interaction
  113. Presentation Layer

  114. View Presenter ViewEntity User Interaction

  115. Use different entities from the interactor and domain layer.

  116. Use different entities from the interactor and domain layer. Decouples

    from Use case modifications.
  117. Use different entities from the interactor and domain layer. Decouples

    from Use case modifications. Represents data for the presentation layer.
  118. Orientation Change

  119. ViewEntityHolder OnDestroy OnCreate Activity save ViewEntity restore ViewEntity onSavedInstanceState remove

    ViewEntity if Activity is finishing then Running
  120. Interactors

  121. One use case per interactor maximize reusability

  122. public interface GetTopArticles { void execute(Callback<List<Article>> articles); }

  123. public class GetTopArticlesImpl implements GetTopArticles { private final TweetRepository tweetRepository;

    … @Override public void execute(final Callback<List<Article>> callback) { tweetRepository.getTimeline(new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Article> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  124. public class GetTopArticlesImpl implements GetTopArticles { private final TweetRepository tweetRepository;

    … @Override public void execute(final Callback<List<Article>> callback) { tweetRepository.getTimeline(new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Article> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  125. public class GetTopArticlesImpl implements GetTopArticles { private final TweetRepository tweetRepository;

    … @Override public void execute(final Callback<List<Article>> callback) { tweetRepository.getTimeline(new Callback<List<Tweet>>() { @Override public void success(Result<List<Tweet>> result) { final List<Article> items = processTweets(result); callback.success(items, null); } @Override public void failure(TwitterException e) { callback.failure(e); } }); } }
  126. Repository

  127. public interface TweetRepository { void getTimeline(final Callback<List<Tweet>> callback); void getTweet(String

    tweetId, final Callback<Tweet> callback); }
  128. public class TweetRepositoryImpl implements TweetRepository { private final TwitterApiClient client;

    ... @Override public void getTimeline(final Callback<List<Tweet>> callback) { client.getTimelineService().homeTimeline(200, true, true, true, true, callback); } }
  129. public class TweetRepositoryImpl implements TweetRepository { private final TwitterApiClient client;

    private final TweetCache cache; ... @Override public void getTimeline(final Callback<List<Tweet>> callback) { if (!cache.isEmpty()) { callback.success(cache.getTimeline()) } else { client.getTimelineService().homeTimeline(200, true, true, true, true,callback); } } }
  130. public class TweetRepositoryImpl implements TweetRepository { private final TwitterApiClient client;

    private final TweetCache cache; private final TweetDB db; ... @Override public void getTimeline(final Callback<List<Tweet>> callback) { if (!cache.isEmpty()) { callback.success(cache.getTimeline()) } else if (!db.isEmpty()) { callback.success(db.getTimeline()) } else { client.getTimelineService().homeTimeline(200, true, true, true, true,callback); } } }
  131. View Presenter Repository Presentation Layer Domain Layer Interactor Entity Entity

    Entity Interactor Interactor Data Layer ViewEntity User Interaction
  132. Clean Architecture Benefits Presentation is decoupled from domain, that allows

    you to change the presentation pattern at anytime without changing any other layer.
  133. Clean Architecture Benefits Domain layer allow new comers to understand

    use cases of the app. Interactor is just a function that gets an input and returns an output. It doesn’t care about representation or data sources. Domain layer can be a Java module!
  134. Clean Architecture Benefits Data layer decouples the rest of the

    app from any change in the datasources and in the entities. Domain layer can be a Java module!
  135. Let’s add infinite layers of separation!!!

  136. None
  137. Disadvantages Leads to Overengineering. Too much boilerplate code.

  138. None
  139. Thanks @fernando_cejas @wendi_ @loganj @joenrv @macarse

  140. Questions? #AndroidDevPro We are hiring! israel@twitter.com Code: https://bit.ly/CleanAndroidRepo