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

Droidcon - Android Poor Programming Patterns

Droidcon - Android Poor Programming Patterns

Every engineer has great intentions when diving into a codebase. You want to make the code beautiful, easy to read, and modular. However as time passes and new feature and quick bug fixes are put in, somehow your Android codebase turned into a mess of multiple levels of inheritance of views and fragments and you’re stuck wondering how you ended up here in the first place.

In my talk I will share with you common poor patterns that every developer first makes when developing in Android and the alternative architecture patterns to ensuring clean code. We will talk about listeners, event driven paradigms and clean architecture.

Droidcon Boston Presentation youtube link: https://www.youtube.com/watch?v=-qajWBeHMVo

1c72c4f846df84aa75984a13d3c014df?s=128

Alice Yuan

March 26, 2018
Tweet

Transcript

  1. @Names_Alice Alice Yuan Senior Android Engineer @ Patreon contact@aliceyuan.ca @Names_Alice

    Poor Programming Patterns and How to Avoid Them
  2. @Names_Alice

  3. @Names_Alice Being a good engineer = Writing lots of code

  4. @Names_Alice code code code code code code code code code

    code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code code Being a good engineer = Writing lots of code
  5. @Names_Alice

  6. @Names_Alice

  7. @Names_Alice Lite Demo App github.com/AliceYuan/DroidConDemo

  8. @Names_Alice github.com/AliceYuan/DroidConDemo


  9. @Names_Alice github.com/AliceYuan/DroidConDemo


  10. @Names_Alice Problem #1

  11. @Names_Alice Simple UI, but a pain to add new features

  12. @Names_Alice #1. Adding new features difficult previous design MyProfileFragment PinnerFragment

  13. @Names_Alice PinnerFragment MyProfileFragment BaseProfileFragment - abstract class #1. Adding new

    features difficult with shared logic
  14. @Names_Alice abstract class BaseProfileFragment extends Fragment { @Nullable protected RoundedImageView

    _profileIv; @Nullable protected TextView _nameTv; @Nullable protected TextView _bioTv; @Nullable protected TextView _followersTv; @Nullable protected LinearLayout _layout; protected void updateView(@NonNull final PDKUser user) { _nameTv.setText(user.getFirstName() + " " + user.getLastName()); _bioTv.setText(user.getBio()); _followersTv.setText(getResources().getString(R.string.following, user.getFollowingCount()); @Names_Alice
  15. @Names_Alice abstract class BaseProfileFragment extends Fragment { @Nullable protected RoundedImageView

    _profileIv; @Nullable protected TextView _nameTv; @Nullable protected TextView _bioTv; @Nullable protected TextView _followersTv; @Nullable protected LinearLayout _layout; protected void updateView(@NonNull final PDKUser user) { _nameTv.setText(user.getFirstName() + " " + user.getLastName()); _bioTv.setText(user.getBio()); _followersTv.setText(getResources().getString(R.string.following, user.getFollowingCount()); Code Smell: Protected member variables @Names_Alice
  16. @Names_Alice abstract class BaseProfileFragment extends Fragment { @Nullable protected RoundedImageView

    _profileIv; @Nullable protected TextView _nameTv; @Nullable protected TextView _bioTv; @Nullable protected TextView _followersTv; @Nullable protected LinearLayout _layout; protected void updateView(@NonNull final PDKUser user) { _nameTv.setText(user.getFirstName() + " " + user.getLastName()); _bioTv.setText(user.getBio()); _followersTv.setText(getResources().getString(R.string.following, user.getFollowingCount()); Code Smell: Logic in base class can change @Names_Alice
  17. @Names_Alice public class MyProfileFragment extends BaseProfileFragment { @Override protected void

    updateView( @NonNull PDKUser user) { // update View and Avatar public class PinnerFragment extends BaseProfileFragment { @Override protected void updateView( @NonNull PDKUser user) { // update View and Avatar @Names_Alice
  18. @Names_Alice public class MyProfileFragment extends BaseProfileFragment { @Override protected void

    updateView( @NonNull PDKUser user) { // update View and Avatar public class PinnerFragment extends BaseProfileFragment { @Override protected void updateView( @NonNull PDKUser user) { // update View and Avatar Code Smell: Overriding and reimplementing logic @Names_Alice
  19. @Names_Alice MyProfileFragment BaseProfileFragment - abstract class PinnerFragment #1. Adding new

    features difficult What if we multiplied the fragments?
  20. @Names_Alice MyProfileFragment BaseProfileFragment - abstract class PinnerFragment #1. Adding new

    features difficult What if we multiplied the fragments? ProfileFragmentA ProfileFragmentB PinnerFragmentA PinnerFragmentB
  21. @Names_Alice MyProfileFragment BaseProfileFragment - abstract class PinnerFragment #1. Adding new

    features difficult What if we multiplied the fragments? ProfileFragmentA ProfileFragmentB PinnerFragmentA PinnerFragmentB Inheritance Hell
  22. @Names_Alice Is a? vs. Has a? Inheritance vs Composition #1.

    Adding new features difficult
  23. @Names_Alice Is a? vs. Has a? Inheritance vs Composition #1.

    Adding new features difficult
  24. @Names_Alice What is the relationship of the common UI? MyProfileFragment

    is an avatar view PinnerFragment is an avatar view OR MyProfileFragment has an avatar view PinnerFragment has an avatar view Is a? vs. Has a? Inheritance vs Composition #1. Adding new features difficult
  25. @Names_Alice Previous architecture PinnerFragment MyProfileFragment BaseProfileFragment - abstract class #1.

    Adding new features difficult
  26. @Names_Alice New architecture MyProfileFragment #1. Adding new features difficult updateView(...)

    AvatarView
  27. @Names_Alice New architecture PinnerFragment #1. Adding new features difficult updateView(...)

    AvatarView
  28. @Names_Alice class AvatarView extends LinearLayout { @Nullable private RoundedImageView _profileIv;

    @Nullable private TextView _nameTv; @Nullable private TextView _bioTv; @Nullable private TextView _followersTv; public void updateView(String name, String imageUrl, String bioText, String followText) { _nameTv.setText(name); _bioTv.setText(bioText); Glide.with(getContext()) .load(imageUrl) .into(_profileIv); } } @Names_Alice #1. Adding new features difficult
  29. @Names_Alice class AvatarView extends LinearLayout { @Nullable private RoundedImageView _profileIv;

    @Nullable private TextView _nameTv; @Nullable private TextView _bioTv; @Nullable private TextView _followersTv; public void updateView(String name, String imageUrl, String bioText, String followText) { _nameTv.setText(name); _bioTv.setText(bioText); Glide.with(getContext()) .load(imageUrl) .into(_profileIv); } } Private member variables @Names_Alice #1. Adding new features difficult
  30. @Names_Alice class AvatarView extends LinearLayout { @Nullable private RoundedImageView _profileIv;

    @Nullable private TextView _nameTv; @Nullable private TextView _bioTv; @Nullable private TextView _followersTv; public void updateView(String name, String imageUrl, String bioText, String followText) { _nameTv.setText(name); _bioTv.setText(bioText); Glide.with(getContext()) .load(imageUrl) .into(_profileIv); } } Pass through custom attributes @Names_Alice #1. Adding new features difficult
  31. @Names_Alice Key Takeaway: Be deliberate with inheritance - think composition

    first #1. Adding new features difficult
  32. @Names_Alice Inheritance is intentional - Declare your class as final

    initially Key Takeaway: Be deliberate with inheritance - think composition first #1. Adding new features difficult public final class MyProfileFragment
  33. @Names_Alice Inheritance is intentional - Declare your class as final

    initially Key Takeaway: Be deliberate with inheritance - think composition first #1. Adding new features difficult public final class MyProfileFragment Use inheritance when the is relationship makes sense Example: a vehicle has tires, a truck is a type of vehicle Example: the android Fragment: when we create a custom Fragment, the custom Fragment is an android fragment
  34. @Names_Alice Problem #2

  35. @Names_Alice #2. Eventbus causing bugs So many bugs related to

    follow button
  36. @Names_Alice PinnerFragment #2. Eventbus causing bugs So many bugs related

    to follow button MyProfileFragment
  37. @Names_Alice PinnerFragment #2. Eventbus causing bugs So many bugs related

    to follow button Fragment2 Ben Silbermann MyProfileFragment
  38. @Names_Alice #2. Eventbus causing bugs So many bugs related to

    follow button PinnerFragment MyProfileFragment Fragment2 Ben Silbermann Fragment3
  39. @Names_Alice EventBus Libraries such as Eventbus, Otto, Tinybus #2. Eventbus

    causing bugs At the implementation level, it is a global event queue.
  40. @Names_Alice EventBus Libraries such as Eventbus, Otto, Tinybus #2. Eventbus

    causing bugs At the implementation level, it is a global event queue.
  41. @Names_Alice Notifying updates - why is it breaking? PinnerFragment #2.

    Eventbus causing bugs Publisher void onFollowButtonClicked() { EventBus.getDefault() .post(new FollowEvent(newFollowingCount)); }
  42. @Names_Alice Notifying updates - why is it breaking? MyProfileFragment #2.

    Eventbus causing bugs onMessageEvent(FollowEvent event) { setFollowingCount(event.getNumFollowing(); } Subscriber
  43. @Names_Alice Subscriber: MyProfileFragment #2. Eventbus causing bugs Notifying updates -

    why is it breaking? Publisher: PinnerFragment
  44. @Names_Alice Subscriber 1 #2. Eventbus causing bugs Notifying updates -

    why is it breaking? Publisher 1 Publisher 2 Subscriber 2 Ben Silbermann
  45. @Names_Alice • There’s no enforced responsibility of ensuring something is

    listening • As we add more events it decreases reliability and maintainability of the code • A pain to write tests for • Thus, only use eventbus when the client does not care if the event is consumed or not eg. Logging events are consumed by the server #2. Eventbus causing bugs Because it’s decoupled, Eventbus libraries have many pitfalls
  46. @Names_Alice Solution: Observer/ Listener Pattern Simple interface to enforce tight

    coupling with an observer and subscriber pattern #2. Eventbus causing bugs public interface FollowListener { void onFollowCountChanged(int count); } FollowListener.java
  47. @Names_Alice Solution: Observer/ Listener Pattern MyProfileFragment #2. Eventbus causing bugs

    public class MyProfileFragment implements FollowListener { //… registerListener in navigation @Override public void onFollowCountChanged(int count) { setFollowingCount(count); } } Subscriber
  48. @Names_Alice Solution: Observer/ Listener Pattern #2. Eventbus causing bugs public

    class PinnerFragment { private FollowListener _followListener; public void registerListener(FollowListener followListener) { _followListener = followListener; } //… Publisher PinnerFragment
  49. @Names_Alice Solution: Observer/ Listener Pattern #2. Eventbus causing bugs //

    network request to get following count… new UserCountApiCallback() { @Override public void onSuccess(int count) { _following = newFollowing; if (_followListener != null) { _followListener.onFollowCountChanged(count); } } } Publisher PinnerFragment
  50. @Names_Alice • UI updates is not a use case that

    benefits from loose coupling • Use event bus for places where loose coupling makes sense • Use an Observable/ Listener pattern otherwise Key Takeaway: EventBus Libraries are often abused due to its simplicity #2. Eventbus causing bugs
  51. @Names_Alice Problem #3

  52. @Names_Alice Do we need to send events to maintain data

    consistency? #3. Poor data consistency MyProfileFragment PinnerFragment
  53. @Names_Alice Why do I even need to send events? #3.

    Poor data consistency MyProfileFragment PinnerFragment PDKUser _myUser; int _followingCount PDKUser _myUser; PDKUser _curUser;
  54. @Names_Alice #3. Poor data consistency

  55. @Names_Alice public class MyProfileFragment extends MVPFragment implements MyProfileView { //cache

    values to avoid having to make network calls in the future private PDKUser _myUser; private int _followingCount; private void loadUser() { if (_myUser == null) { loadMyUserAPI(); } else { _avatarView.updateView(_myUser.getFirstName() + " " + _myUser.getLastName(), MyUserUtils.get().getLargeImageUrl(_myUser), _myUser.getBio()); updateFollowingCount(_followingCount); } } @Names_Alice #3. Poor data consistency
  56. @Names_Alice public class MyProfileFragment extends MVPFragment implements MyProfileView { //cache

    values to avoid having to make network calls in the future private PDKUser _myUser; private int _followingCount; private void loadUser() { if (_myUser == null) { loadMyUserAPI(); } else { _avatarView.updateView(_myUser.getFirstName() + " " + _myUser.getLastName(), MyUserUtils.get().getLargeImageUrl(_myUser), _myUser.getBio()); updateFollowingCount(_followingCount); } } Code Smell: Caching models on fragment basis @Names_Alice #3. Poor data consistency
  57. @Names_Alice public class MyProfileFragment extends MVPFragment implements MyProfileView { //cache

    values to avoid having to make network calls in the future private PDKUser _myUser; private int _followingCount; private void loadUser() { if (_myUser == null) { loadMyUserAPI(); } else { _avatarView.updateView(_myUser.getFirstName() + " " + _myUser.getLastName(), MyUserUtils.get().getLargeImageUrl(_myUser), _myUser.getBio()); updateFollowingCount(_followingCount); } } Code Smell: Model dependent logic on the view layer @Names_Alice #3. Poor data consistency
  58. @Names_Alice Other code smells which indicate poor data consistency #3.

    Poor data consistency • Our example: UI instances tracking model state • More examples:
  59. @Names_Alice Other code smells which indicate poor data consistency #3.

    Poor data consistency • Our example: UI instances tracking model state • More examples: • Global static variables public static PDKUser myUser = null;
  60. @Names_Alice Other code smells which indicate poor data consistency #3.

    Poor data consistency • Our example: UI instances tracking model state • More examples: • Global static variables • Variables hidden through a singletons. Often a utils class pattern public static PDKUser myUser = null; class MyUserUtils { String doSomethingWithUserName(String userName) { myUser.setUserName(userName); // ... logic happens }
  61. @Names_Alice Solution: have a central area to handle
 all storing

    and retrieval of models Fragment Models Repository Memory Cache Disk Cache Networking #3. Poor data consistency
  62. @Names_Alice Solution Code: Repository of User models #3. Poor data

    consistency
  63. @Names_Alice interface RepositoryListener<M extends Object> { void onSuccess(M model); void

    onError(Exception e); } @Names_Alice #3. Poor data consistency
  64. @Names_Alice public class UserRepository { private PDKUser _myUser; //... public

    void loadMyUser(@NonNull final RepositoryListener<PDKUser> listener) { if (_myUser != null) { listener.onSuccess(_myUser); return; } PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override public void onSuccess(PDKResponse response) { _myUser = response.getUser(); listener.onSuccess(_myUser); } @Names_Alice #3. Poor data consistency
  65. @Names_Alice public class UserRepository { private PDKUser _myUser; //... public

    void loadMyUser(@NonNull final RepositoryListener<PDKUser> listener) { if (_myUser != null) { listener.onSuccess(_myUser); return; } PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override public void onSuccess(PDKResponse response) { _myUser = response.getUser(); listener.onSuccess(_myUser); } Central cache check @Names_Alice #3. Poor data consistency
  66. @Names_Alice public class UserRepository { private PDKUser _myUser; //... public

    void loadMyUser(@NonNull final RepositoryListener<PDKUser> listener) { if (_myUser != null) { listener.onSuccess(_myUser); return; } PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override public void onSuccess(PDKResponse response) { _myUser = response.getUser(); listener.onSuccess(_myUser); } Central network call
 & update model @Names_Alice #3. Poor data consistency
  67. @Names_Alice public class MyProfileFragment extends Fragment { private AvatarView _avatarView;

    //... private void loadMyUser() { UserRepository.get().loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { _avatarView.updateView(user.getFirstName() + " " + user.getLastName(), user.getImageUrl(), user.getBio()); } }); } @Names_Alice #3. Poor data consistency
  68. @Names_Alice public class MyProfileFragment extends Fragment { private AvatarView _avatarView;

    //... private void loadMyUser() { UserRepository.get().loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { _avatarView.updateView(user.getFirstName() + " " + user.getLastName(), user.getImageUrl(), user.getBio()); } }); } Simple call to update view @Names_Alice #3. Poor data consistency
  69. @Names_Alice • Lots of asynchronous communication problems cannot be easily

    solved with a listener pattern • More complex example: chaining data calls, returning more than one type data response • rxJava can solve this through Observable stream • There exist libraries that adapt network callbacks into rxJava Observables for you Repository with RxJava #3. Poor data consistency
  70. @Names_Alice Key Takeaway: Build a central way to fetch and

    retrieve models #3. Poor data consistency • Stop storing instances of models in your fragments!
 • Ensure data consistency regardless of where we’re retrieving or storing our models
 • Store and fetch models in a central area
  71. @Names_Alice Final issue, Problem #4

  72. @Names_Alice No unit tests :( Why is writing unit tests

    so difficult? • We want to ensure that we are correctly setting the user profile display data • What makes writing this unit test so complex? #4. Writing unit tests is hard
  73. @Names_Alice What a typical fragment looks like Fragment UI Animation

    Networking Models Business Logic Caching Logging Android Services #4. Writing unit tests is hard
  74. @Names_Alice What a typical fragment looks like Fragment UI Animation

    Networking Models Business Logic Caching Logging Android Services Unit test #4. Writing unit tests is hard
  75. @Names_Alice private void loadMyUser() { PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override

    public void onSuccess(PDKResponse response) { ... PDKUser user = response.getUser(); _myAvatarView.setUser(user); } class MyProfileFragment @Names_Alice #4. Writing unit tests is hard
  76. @Names_Alice private void loadMyUser() { PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override

    public void onSuccess(PDKResponse response) { ... PDKUser user = response.getUser(); _myAvatarView.setUser(user); } class MyProfileFragment Mock network callback @Names_Alice #4. Writing unit tests is hard
  77. @Names_Alice private void loadMyUser() { PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override

    public void onSuccess(PDKResponse response) { ... PDKUser user = response.getUser(); _myAvatarView.setUser(user); } class MyProfileFragment Mock Translation of Response to Model @Names_Alice #4. Writing unit tests is hard
  78. @Names_Alice private void loadMyUser() { PDKClient.getInstance().getMe(USER_FIELDS, new PDKCallback() { @Override

    public void onSuccess(PDKResponse response) { ... PDKUser user = response.getUser(); _myAvatarView.setUser(user); } class MyProfileFragment Mock Android Framework UI using Roboelectric @Names_Alice #4. Writing unit tests is hard
  79. @Names_Alice Let’s make this simpler, How should a unit test

    look like? #4. Writing unit tests is hard
  80. @Names_Alice @Test public void testLoadMyUserSuccess() throws Exception { verify(_myProfileView).updateAvatarView(FIRST_NAME +

    " " + LAST_NAME, IMAGE_URL, BIO); verify(_myProfileView).updateFollowingText(); } @Names_Alice #4. Writing unit tests is hard
  81. @Names_Alice Solution: Separate concerns through an interface You’ve likely heard

    of the paradigms MVVM, MVP and MVI (Model-View-View-Model, Model-View-Presenter, Model-View-Intent)
 Key value: they separate concerns between areas that do not need to know about each other. 
 You can now communicate between classes without knowing the internals #4. Writing unit tests is hard
  82. @Names_Alice Separate concerns - MVP example Contract Presenter (Business Logic)

    Fragment View Interface Models (Repository) #4. Writing unit tests is hard Contract Data Source
  83. @Names_Alice Separate concerns - MVP example Contract Presenter (Business Logic)

    Fragment View Interface Models (Repository) #4. Writing unit tests is hard Contract Data Source
  84. @Names_Alice Define a Contract for the View #4. Writing unit

    tests is hard
  85. @Names_Alice interface MyProfileView extends MVPView { 
 void updateAvatarView(String name,

    String imageUrl, String bioText); 
 void updateFollowingCount(int count); } @Names_Alice #4. Writing unit tests is hard
  86. @Names_Alice public class MyProfileFragment extends MVPFragment implements MyProfileView { @Override

    public void updateAvatarView(String name, String imageUrl, String bioText) { _avatarView.updateView(name, imageUrl, bioText); } @Override public void updateFollowingCount(int count) { _avatarView.updateFollowingText(getResources().getString(
 R.string.my_user_following, count)); } } @Names_Alice #4. Writing unit tests is hard
  87. @Names_Alice Define a Contract for the Repository #4. Writing unit

    tests is hard
  88. @Names_Alice public interface UserDataSource { void loadMyUser(@NonNull final RepositoryListener<PDKUser> listener);

    void loadMyUserNumFollowing(@NonNull final RepositoryListener<Integer> listener); }
 @Names_Alice #4. Writing unit tests is hard
  89. @Names_Alice public interface UserDataSource { void loadMyUser(@NonNull final RepositoryListener<PDKUser> listener);

    void loadMyUserNumFollowing(@NonNull final RepositoryListener<Integer> listener); }
 
 public class UserRepository implements UserDataSource { @Names_Alice #4. Writing unit tests is hard
  90. @Names_Alice public class MyProfilePresenter implements Presenter<MyProfileView> { public MyProfilePresenter(@NonNull UserDataSource

    dataSource) { _dataSource = dataSource; } @Override public void attachView(@NonNull final MyProfileView view) { _view = view; loadUser(view); } void loadUser(final MyProfileView view) { _dataSource.loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { @Names_Alice #4. Writing unit tests is hard
  91. @Names_Alice public class MyProfilePresenter implements Presenter<MyProfileView> { public MyProfilePresenter(@NonNull UserDataSource

    dataSource) { _dataSource = dataSource; } @Override public void attachView(@NonNull final MyProfileView view) { _view = view; loadUser(view); } void loadUser(final MyProfileView view) { _dataSource.loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { No longer referencing repository @Names_Alice #4. Writing unit tests is hard
  92. @Names_Alice public class MyProfilePresenter implements Presenter<MyProfileView> { public MyProfilePresenter(@NonNull UserDataSource

    dataSource) { _dataSource = dataSource; } @Override public void attachView(@NonNull final MyProfileView view) { _view = view; loadUser(view); } void loadUser(final MyProfileView view) { _dataSource.loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { No longer need to mock Android Framework view @Names_Alice #4. Writing unit tests is hard
  93. @Names_Alice @Override public void attachView(@NonNull final MyProfileView view) { _view

    = view; loadUser(view); } void loadUser(final MyProfileView view) { _dataSource.loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { view.updateAvatarView(user.getFirstName() + " " + user.getLastName(), user.getImageUrl(), user.getBio()); } } }); #4. Writing unit tests is hard
  94. @Names_Alice @Override public void attachView(@NonNull final MyProfileView view) { _view

    = view; loadUser(view); } void loadUser(final MyProfileView view) { _dataSource.loadMyUser(new RepositoryListener<PDKUser>() { @Override public void onSuccess(PDKUser user) { view.updateAvatarView(user.getFirstName() + " " + user.getLastName(), user.getImageUrl(), user.getBio()); } } }); No longer mocking network callback #4. Writing unit tests is hard
  95. @Names_Alice interface Presenter<V extends MVPView> { void attachView(@NonNull final V

    view); void detachView();
 } interface MVPView { Presenter createPresenter(); Presenter getPresenter(); } *Requires a MVP framework to function #4. Writing unit tests is hard
  96. @Names_Alice What does writing unit tests look like now? #4.

    Writing unit tests is hard
  97. @Names_Alice @Test public void testLoadMyUserSuccess() throws Exception { verify(_myProfileView).updateAvatarView(FIRST_NAME +

    " " + LAST_NAME, IMAGE_URL, BIO); verify(_viewResources).getString(anyInt(), eq(_followingUsersList.size())); verify(_myProfileView).updateFollowingText(); } @Names_Alice #4. Writing unit tests is hard
  98. @Names_Alice public static abstract class BaseMyProfileTest { @Mock MyProfileView _myProfileView;

    UserDataSource _mockDataSource; @Mock ViewResources _viewResources; @Before public void setUp() throws Exception { _mockDataSource = getUserDataSource(); MyProfilePresenter myProfilePresenter = new MyProfilePresenter(_viewResources, _mockDataSource); myProfilePresenter.attachView(_myProfileView); } abstract UserDataSource getUserDataSource(); } *MVP Testing Infra not shown @Names_Alice #4. Writing unit tests is hard
  99. @Names_Alice public static abstract class BaseMyProfileTest { @Mock MyProfileView _myProfileView;

    UserDataSource _mockDataSource; @Mock ViewResources _viewResources; @Before public void setUp() throws Exception { _mockDataSource = getUserDataSource(); MyProfilePresenter myProfilePresenter = new MyProfilePresenter(_viewResources, _mockDataSource); myProfilePresenter.attachView(_myProfileView); } abstract UserDataSource getUserDataSource(); } *MVP Testing Infra not shown Mock interfaces using Mockito @Names_Alice #4. Writing unit tests is hard
  100. @Names_Alice Separation of Concerns makes the code cleaner! • Improves

    understandability of codebase • view updates can be quite long and that detracts from understanding logic of the codebase • Increases reusability of the codebase, views can be reused
 • Can also be used in building libraries and modularizing the codebase
 #4. Writing unit tests is hard
  101. @Names_Alice Key takeaway: Unit tests are easy to write when

    you separate the business logic • Choose a paradigm (MVP, MVVM) to follow which separates concerns
 • Use interfaces to abstract internals away and use a mocking library eg. Mockito to mock functionality
 • Improves testability and also understandability of the code #4. Writing unit tests is hard
  102. @Names_Alice We made it, that’s all!

  103. @Names_Alice SOLID Principle S — Single Responsibility Principle(S.R.P) O — Open-Closed Principle L — Liskov Substitution

    Principle I — Interface Segregation Principle D — Dependency Inversion Principle
  104. @Names_Alice 1. Be deliberate about inheritance, think about composition 2.

    Eventbus is a loosely coupled library - use tight coupling patterns such as RxJava or Observable callbacks instead 3. Create a central location to store and retrieve models to ensure data consistency 4. Separate the areas of concern to increase testability and maintenance Recap
  105. @Names_Alice Being a good engineer ≠ Writing lots of code

  106. @Names_Alice Being a good engineer = Being intentional

  107. @Names_Alice Suggested Reading Materials 1. Creating highly modular android apps

    https://medium.com/stories-from-eyeem/ creating-highly-modular-android-apps-933271fbdb7d 2. It’s the 21st century — STOP using EVENTBUS! https://medium.com/ @gmirchev90/its-21st-century-stop-using-eventbus-3ff5d9c6a00f 3. MVC vs MVP vs MVVM vs MVI? https://academy.realm.io/posts/mvc-vs-mvp-vs- mvvm-vs-mvi-mobilization-moskala/ 4. Implementing MVVM using LiveData, RxJava and Dagger https:// proandroiddev.com/mvvm-architecture-using-livedata-rxjava-and-new-dagger- android-injection-639837b1eb6c 5. SOLID Principles made easy https://hackernoon.com/solid-principles-made- easy-67b1246bcdf
  108. You ask me questions at: contact@aliceyuan.ca @Names_Alice Find my slides

    at: https://goo.gl/Ksn4QH