Slide 1

Slide 1 text

Journey to 99% Crash Free Fumihiko Shiroyama

Slide 2

Slide 2 text

Slide URL • https://goo.gl/zwxdyy

Slide 3

Slide 3 text

About Me • Fumihiko Shiroyama • Android App Developer • Nikkei Inc. • https://github.com/srym • https://twitter.com/fushiroyama

Slide 4

Slide 4 text

Agenda • Face up to the reality of crash! • Take the first step for Unit Testing • Make biggest gains without Unit Testing

Slide 5

Slide 5 text

The Fact Your App is Crashing

Slide 6

Slide 6 text

The Fact Your App is Crashing • 99% Crash Free? Good! • DAU 30000

Slide 7

Slide 7 text

The Fact Your App is Crashing • 99% Crash Free? Good! • DAU 30000 $SBTIFT

Slide 8

Slide 8 text

The Fact Your App is Crashing • 99% Crash Free? Good! • DAU 30000 • 99.9% Crash Free? Great!!

Slide 9

Slide 9 text

The Fact Your App is Crashing • 99% Crash Free? Good! • DAU 30000 • 99.9% Crash Free? Great!! 4UJMM$SBTIFT

Slide 10

Slide 10 text

Keep Going!!!

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Fabric • Crashlytics • Beta • Answers • Acquired by Google

Slide 13

Slide 13 text

"41MVHJO

Slide 14

Slide 14 text

Crashlytics • Automatically collects crash info • No need for uploading obfuscation mappings • Crashlytics#logException(e) for non-fatal

Slide 15

Slide 15 text

Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345"); Crashlytics.setUserEmail("[email protected]"); Crashlytics.setUserName("Test User"); }

Slide 16

Slide 16 text

Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345"); Crashlytics.setUserEmail("[email protected]"); Crashlytics.setUserName("Test User"); }

Slide 17

Slide 17 text

Firebase Crash Reporting • Similar to Crashlytics

Slide 18

Slide 18 text

"41MVHJO

Slide 19

Slide 19 text

Crash Reporting • Automatically collects crash info as well • Need for uploading obfuscation mappings (there is a helper gradle plugin) • FirebaseCrash.report(e) for non-fatal

Slide 20

Slide 20 text

Crash Reporting Normal Stack Trace

Slide 21

Slide 21 text

Crash Reporting Device Info

Slide 22

Slide 22 text

Crash Reporting Contextual History

Slide 23

Slide 23 text

Crash Reporting Contextual History $00- &WFOU -PH

Slide 24

Slide 24 text

Integration

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Timber dependencies { compile 'com.jakewharton.timber:timber:4.5.1' }

Slide 27

Slide 27 text

Timber dependencies { compile 'com.jakewharton.timber:timber:4.5.1' } #FUUFS-PHE

Slide 28

Slide 28 text

Timber public class CrashReportingTree extends Timber.Tree { @Override protected void log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } }

Slide 29

Slide 29 text

Timber public class CrashReportingTree extends Timber.Tree { @Override protected void log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } }

Slide 30

Slide 30 text

Timber public class CrashReportingTree extends Timber.Tree { @Override protected void log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } }

Slide 31

Slide 31 text

Timber public class CrashReportingTree extends Timber.Tree { @Override protected void log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } } /PO'BUBM

Slide 32

Slide 32 text

Timber public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); initTimber(); } private void initTimber() { if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } } }

Slide 33

Slide 33 text

Timber public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); initTimber(); } private void initTimber() { if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } } }

Slide 34

Slide 34 text

Timber public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); initTimber(); } private void initTimber() { if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } } }

Slide 35

Slide 35 text

Timber Timber.d("log message"); // or Timber.e(error, error.getMessage());

Slide 36

Slide 36 text

Take the first step for Unit Testing

Slide 37

Slide 37 text

Why can't you write test?

Slide 38

Slide 38 text

Typical Case

Slide 39

Slide 39 text

Fat Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }

Slide 40

Slide 40 text

Fat Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }

Slide 41

Slide 41 text

Fat Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }

Slide 42

Slide 42 text

Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); }

Slide 43

Slide 43 text

Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); } pFMEBDDFTT

Slide 44

Slide 44 text

How do I test MyActivity#fetchUser ?

Slide 45

Slide 45 text

That's impossible

Slide 46

Slide 46 text

Make it testable private User fetchUser(long userId) { return apiClient.findUserById(userId); }

Slide 47

Slide 47 text

Make it testable private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); }

Slide 48

Slide 48 text

private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); } 3FNPWFE pFMEBDDFTT Make it testable

Slide 49

Slide 49 text

private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); } Make it testable

Slide 50

Slide 50 text

public class UserFetcher { private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); } } Make it testable

Slide 51

Slide 51 text

public class UserFetcher { public User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); } } Make it testable

Slide 52

Slide 52 text

public class UserFetcher { private final ApiClient apiClient; public UserFetcher(ApiClient apiClient) { this.apiClient = apiClient; } public User fetchUser(long userId) { return apiClient.findUserById(userId); } } Make it testable

Slide 53

Slide 53 text

public class UserFetcher { private final ApiClient apiClient; public UserFetcher(ApiClient apiClient) { this.apiClient = apiClient; } public User fetchUser(long userId) { return apiClient.findUserById(userId); } } Make it testable

Slide 54

Slide 54 text

Make it testable • This is so-called Dependency Injection • DI can be done without any libraries! • You can use DI library at any time though

Slide 55

Slide 55 text

Anything else?

Slide 56

Slide 56 text

Problem is Activity

Slide 57

Slide 57 text

Everything is in Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }

Slide 58

Slide 58 text

Let's split this into parts!

Slide 59

Slide 59 text

MVP

Slide 60

Slide 60 text

What is MVP • Model - View - Presenter • View…Activity (Fragment) • Model…Business Logic (e.g. fetching data) • Presenter…Bridge between V & M

Slide 61

Slide 61 text

Introduce MVP public interface UserContract { public interface View { void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }

Slide 62

Slide 62 text

Introduce MVP public interface UserContract { public interface View { void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } } $POUSBDUCFUXFFO 7JFXBOE1SFTFOUFS

Slide 63

Slide 63 text

Introduce MVP public interface UserContract { public interface View { void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }

Slide 64

Slide 64 text

Introduce MVP public interface UserContract { public interface View { void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } } $BMMCBDLGSPN 1SFTFOUFS

Slide 65

Slide 65 text

Introduce MVP public interface UserContract { public interface View { void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }

Slide 66

Slide 66 text

Introduce MVP public interface UserContract { public interface View { void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } } &WFOUGSPN 6*

Slide 67

Slide 67 text

Introduce MVP public class UserPresenter implements UserContract.Interaction { private final UserContract.View view; private final UserFetcher userFetcher; public UserPresenter(UserContract.View view, UserFetcher userFetcher) { this.view = view; this.userFetcher = userFetcher; } @Override public void clickButton(long userId) { } }

Slide 68

Slide 68 text

Introduce MVP public class UserPresenter implements UserContract.Interaction { private final UserContract.View view; private final UserFetcher userFetcher; public UserPresenter(UserContract.View view, UserFetcher userFetcher) { this.view = view; this.userFetcher = userFetcher; } @Override public void clickButton(long userId) { } }

Slide 69

Slide 69 text

Introduce MVP public class UserPresenter implements UserContract.Interaction { private final UserContract.View view; private final UserFetcher userFetcher; public UserPresenter(UserContract.View view, UserFetcher userFetcher) { this.view = view; this.userFetcher = userFetcher; } @Override public void clickButton(long userId) { } }

Slide 70

Slide 70 text

Introduce MVP @Override public void clickButton(long userId) { userFetcher.findUserAsync(userId, new UserFetcher.OnSuccessCallback() { @Override public void onSuccess(User user) { view.showUser(user); } }, new UserFetcher.OnFailureCallback() { @Override public void onFailure(Exception e) { view.showError(e); } }); }

Slide 71

Slide 71 text

Introduce MVP @Override public void clickButton(long userId) { userFetcher.findUserAsync(userId, new UserFetcher.OnSuccessCallback() { @Override public void onSuccess(User user) { view.showUser(user); } }, new UserFetcher.OnFailureCallback() { @Override public void onFailure(Exception e) { view.showError(e); } }); }

Slide 72

Slide 72 text

Introduce MVP @Override public void clickButton(long userId) { userFetcher.findUserAsync(userId, new UserFetcher.OnSuccessCallback() { @Override public void onSuccess(User user) { view.showUser(user); } }, new UserFetcher.OnFailureCallback() { @Override public void onFailure(Exception e) { view.showError(e); } }); }

Slide 73

Slide 73 text

Introduce MVP public class MyActivity extends BaseActivity implements UserContract.View { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void showUser(User user) {} @Override public void showError(Exception error) {} }

Slide 74

Slide 74 text

Introduce MVP public class MyActivity extends BaseActivity implements UserContract.View { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void showUser(User user) {} @Override public void showError(Exception error) {} }

Slide 75

Slide 75 text

Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }

Slide 76

Slide 76 text

Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }

Slide 77

Slide 77 text

Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }

Slide 78

Slide 78 text

Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }

Slide 79

Slide 79 text

Result • Now objects' responsibilities are clearer • Dependency is provided at constructor • Thus testable!

Slide 80

Slide 80 text

Make biggest gains without Unit Testing

Slide 81

Slide 81 text

It turned out...

Slide 82

Slide 82 text

Quite a few crashes were caused by...

Slide 83

Slide 83 text

IllegalStateException java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

Slide 84

Slide 84 text

IllegalStateException java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 'SBHNFOU5SBOTBDUJPODPNNJU

Slide 85

Slide 85 text

FragmentTransaction • FragmentTransaction#commit • DialogFragment#show

Slide 86

Slide 86 text

FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }

Slide 87

Slide 87 text

FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }

Slide 88

Slide 88 text

Don't commit after... • AsyncTask#onPostExecute • LoaderCallbacks#onLoadFinished • Actually ANY KINDS OF ASYNC CALLBACKS are not safe

Slide 89

Slide 89 text

RxLifecycle?

Slide 90

Slide 90 text

RxLifecycle • RxLifecycle IS GREAT!! • Not all programmers, nor projects always need this. • Vulture is for you!

Slide 91

Slide 91 text

Vulture • https://github.com/srym/vulture • Handles async callback safely • Based on PauseHandler* picture from wikimedia.org * https://goo.gl/g6w3CS

Slide 92

Slide 92 text

Vulture void fetchAsynchronously() { /* do heavy asynchronous task here */ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }

Slide 93

Slide 93 text

Vulture void fetchAsynchronously() { /* do heavy asynchronous task here */ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }

Slide 94

Slide 94 text

Vulture void fetchAsynchronously() { /* do heavy asynchronous task here */ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }

Slide 95

Slide 95 text

Vulture void fetchAsynchronously() { /* do heavy asynchronous task here */ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } 6OTBGF

Slide 96

Slide 96 text

Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously() { SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }

Slide 97

Slide 97 text

Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously() { SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }

Slide 98

Slide 98 text

Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously() { SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }

Slide 99

Slide 99 text

Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }

Slide 100

Slide 100 text

Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }

Slide 101

Slide 101 text

Supported Types • Primitive types and its boxed types • String, Bundle, Parcelable, Serializable • ParcelableArray • ParcelableArrayList

Slide 102

Slide 102 text

Vulture • Currently Beta release • I need your feedbacks! picture from wikimedia.org

Slide 103

Slide 103 text

Thank You!

Slide 104

Slide 104 text

Special Thanks • Illustrations by ͍Β͢ͱ΍ • http://www.irasutoya.com/ • Thank you so much!!! • https://www.wikimedia.org/ • All the audiences! Thank you!