Slide 1

Slide 1 text

@Names_Alice Alice Yuan Senior Android Engineer @ Patreon [email protected] @Names_Alice Poor Programming Patterns and How to Avoid Them

Slide 2

Slide 2 text

@Names_Alice

Slide 3

Slide 3 text

@Names_Alice Being a good engineer = Writing lots of code

Slide 4

Slide 4 text

@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

Slide 5

Slide 5 text

@Names_Alice

Slide 6

Slide 6 text

@Names_Alice

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

@Names_Alice github.com/AliceYuan/DroidConDemo


Slide 9

Slide 9 text

@Names_Alice github.com/AliceYuan/DroidConDemo


Slide 10

Slide 10 text

@Names_Alice Problem #1

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

@Names_Alice PinnerFragment MyProfileFragment BaseProfileFragment - abstract class #1. Adding new features difficult with shared logic

Slide 14

Slide 14 text

@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

Slide 15

Slide 15 text

@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

Slide 16

Slide 16 text

@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

Slide 17

Slide 17 text

@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

Slide 18

Slide 18 text

@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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

@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

Slide 25

Slide 25 text

@Names_Alice Previous architecture PinnerFragment MyProfileFragment BaseProfileFragment - abstract class #1. Adding new features difficult

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

@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

Slide 29

Slide 29 text

@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

Slide 30

Slide 30 text

@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

Slide 31

Slide 31 text

@Names_Alice Key Takeaway: Be deliberate with inheritance - think composition first #1. Adding new features difficult

Slide 32

Slide 32 text

@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

Slide 33

Slide 33 text

@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

Slide 34

Slide 34 text

@Names_Alice Problem #2

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

@Names_Alice Notifying updates - why is it breaking? MyProfileFragment #2. Eventbus causing bugs onMessageEvent(FollowEvent event) { setFollowingCount(event.getNumFollowing(); } Subscriber

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

@Names_Alice Subscriber 1 #2. Eventbus causing bugs Notifying updates - why is it breaking? Publisher 1 Publisher 2 Subscriber 2 Ben Silbermann

Slide 45

Slide 45 text

@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

Slide 46

Slide 46 text

@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

Slide 47

Slide 47 text

@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

Slide 48

Slide 48 text

@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

Slide 49

Slide 49 text

@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

Slide 50

Slide 50 text

@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

Slide 51

Slide 51 text

@Names_Alice Problem #3

Slide 52

Slide 52 text

@Names_Alice Do we need to send events to maintain data consistency? #3. Poor data consistency MyProfileFragment PinnerFragment

Slide 53

Slide 53 text

@Names_Alice Why do I even need to send events? #3. Poor data consistency MyProfileFragment PinnerFragment PDKUser _myUser; int _followingCount PDKUser _myUser; PDKUser _curUser;

Slide 54

Slide 54 text

@Names_Alice #3. Poor data consistency

Slide 55

Slide 55 text

@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

Slide 56

Slide 56 text

@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

Slide 57

Slide 57 text

@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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

@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;

Slide 60

Slide 60 text

@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 }

Slide 61

Slide 61 text

@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

Slide 62

Slide 62 text

@Names_Alice Solution Code: Repository of User models #3. Poor data consistency

Slide 63

Slide 63 text

@Names_Alice interface RepositoryListener { void onSuccess(M model); void onError(Exception e); } @Names_Alice #3. Poor data consistency

Slide 64

Slide 64 text

@Names_Alice public class UserRepository { private PDKUser _myUser; //... public void loadMyUser(@NonNull final RepositoryListener 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

Slide 65

Slide 65 text

@Names_Alice public class UserRepository { private PDKUser _myUser; //... public void loadMyUser(@NonNull final RepositoryListener 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

Slide 66

Slide 66 text

@Names_Alice public class UserRepository { private PDKUser _myUser; //... public void loadMyUser(@NonNull final RepositoryListener 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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

@Names_Alice public class MyProfileFragment extends Fragment { private AvatarView _avatarView; //... private void loadMyUser() { UserRepository.get().loadMyUser(new RepositoryListener() { @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

Slide 69

Slide 69 text

@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

Slide 70

Slide 70 text

@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

Slide 71

Slide 71 text

@Names_Alice Final issue, Problem #4

Slide 72

Slide 72 text

@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

Slide 73

Slide 73 text

@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

Slide 74

Slide 74 text

@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

Slide 75

Slide 75 text

@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

Slide 76

Slide 76 text

@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

Slide 77

Slide 77 text

@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

Slide 78

Slide 78 text

@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

Slide 79

Slide 79 text

@Names_Alice Let’s make this simpler, How should a unit test look like? #4. Writing unit tests is hard

Slide 80

Slide 80 text

@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

Slide 81

Slide 81 text

@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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

@Names_Alice Define a Contract for the View #4. Writing unit tests is hard

Slide 85

Slide 85 text

@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

Slide 86

Slide 86 text

@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

Slide 87

Slide 87 text

@Names_Alice Define a Contract for the Repository #4. Writing unit tests is hard

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

@Names_Alice public interface UserDataSource { void loadMyUser(@NonNull final RepositoryListener listener); void loadMyUserNumFollowing(@NonNull final RepositoryListener listener); }
 
 public class UserRepository implements UserDataSource { @Names_Alice #4. Writing unit tests is hard

Slide 90

Slide 90 text

@Names_Alice public class MyProfilePresenter implements Presenter { 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() { @Override public void onSuccess(PDKUser user) { @Names_Alice #4. Writing unit tests is hard

Slide 91

Slide 91 text

@Names_Alice public class MyProfilePresenter implements Presenter { 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() { @Override public void onSuccess(PDKUser user) { No longer referencing repository @Names_Alice #4. Writing unit tests is hard

Slide 92

Slide 92 text

@Names_Alice public class MyProfilePresenter implements Presenter { 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() { @Override public void onSuccess(PDKUser user) { No longer need to mock Android Framework view @Names_Alice #4. Writing unit tests is hard

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

@Names_Alice @Override public void attachView(@NonNull final MyProfileView view) { _view = view; loadUser(view); } void loadUser(final MyProfileView view) { _dataSource.loadMyUser(new RepositoryListener() { @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

Slide 95

Slide 95 text

@Names_Alice interface Presenter { 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

Slide 96

Slide 96 text

@Names_Alice What does writing unit tests look like now? #4. Writing unit tests is hard

Slide 97

Slide 97 text

@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

Slide 98

Slide 98 text

@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

Slide 99

Slide 99 text

@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

Slide 100

Slide 100 text

@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

Slide 101

Slide 101 text

@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

Slide 102

Slide 102 text

@Names_Alice We made it, that’s all!

Slide 103

Slide 103 text

@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

Slide 104

Slide 104 text

@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

Slide 105

Slide 105 text

@Names_Alice Being a good engineer ≠ Writing lots of code

Slide 106

Slide 106 text

@Names_Alice Being a good engineer = Being intentional

Slide 107

Slide 107 text

@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

Slide 108

Slide 108 text

You ask me questions at: [email protected] @Names_Alice Find my slides at: https://goo.gl/Ksn4QH