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

Journey to 99% Crash Free

Journey to 99% Crash Free

DroidKaigi 2017 Day 2 (17:10 - 17:40 Room 1)
「テスト0から目指すクラッシュフリー率99%」

3cca191bf3064fd059ea2c3d6022afbd?s=128

Fumihiko Shiroyama

March 09, 2017
Tweet

Transcript

  1. Journey to 99% Crash Free Fumihiko Shiroyama

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

  3. About Me • Fumihiko Shiroyama • Android App Developer •

    Nikkei Inc. • https://github.com/srym • https://twitter.com/fushiroyama
  4. Agenda • Face up to the reality of crash! •

    Take the first step for Unit Testing • Make biggest gains without Unit Testing
  5. The Fact Your App is Crashing

  6. The Fact Your App is Crashing • 99% Crash Free?

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

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

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

    Good! • DAU 30000 • 99.9% Crash Free? Great!! 4UJMM$SBTIFT
  10. Keep Going!!!

  11. None
  12. Fabric • Crashlytics • Beta • Answers • Acquired by

    Google
  13. "41MVHJO

  14. Crashlytics • Automatically collects crash info • No need for

    uploading obfuscation mappings • Crashlytics#logException(e) for non-fatal
  15. Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345");

    Crashlytics.setUserEmail("user@fabric.io"); Crashlytics.setUserName("Test User"); }
  16. Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345");

    Crashlytics.setUserEmail("user@fabric.io"); Crashlytics.setUserName("Test User"); }
  17. Firebase Crash Reporting • Similar to Crashlytics

  18. "41MVHJO

  19. 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
  20. Crash Reporting Normal Stack Trace

  21. Crash Reporting Device Info

  22. Crash Reporting Contextual History

  23. Crash Reporting Contextual History $00- &WFOU -PH

  24. Integration

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

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

  28. 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); } } }
  29. 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); } } }
  30. 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); } } }
  31. 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
  32. 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()); } } }
  33. 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()); } } }
  34. 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()); } } }
  35. Timber Timber.d("log message"); // or Timber.e(error, error.getMessage());

  36. Take the first step for Unit Testing

  37. Why can't you write test?

  38. Typical Case

  39. 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); } }); }
  40. 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); } }); }
  41. 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); } }); }
  42. Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); }

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

    pFMEBDDFTT
  44. How do I test MyActivity#fetchUser ?

  45. That's impossible

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

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

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

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

    Make it testable
  50. public class UserFetcher { private User fetchUser(ApiClient apiClient, long userId)

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

    { return apiClient.findUserById(userId); } } Make it testable
  52. 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
  53. 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
  54. 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
  55. Anything else?

  56. Problem is Activity

  57. 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); } }); }
  58. Let's split this into parts!

  59. MVP

  60. What is MVP • Model - View - Presenter •

    View…Activity (Fragment) • Model…Business Logic (e.g. fetching data) • Presenter…Bridge between V & M
  61. Introduce MVP public interface UserContract { public interface View {

    void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
  62. 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
  63. Introduce MVP public interface UserContract { public interface View {

    void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
  64. 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
  65. Introduce MVP public interface UserContract { public interface View {

    void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
  66. 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*
  67. 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) { } }
  68. 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) { } }
  69. 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) { } }
  70. 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); } }); }
  71. 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); } }); }
  72. 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); } }); }
  73. 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) {} }
  74. 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) {} }
  75. 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()); } }
  76. 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()); } }
  77. 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()); } }
  78. 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()); } }
  79. Result • Now objects' responsibilities are clearer • Dependency is

    provided at constructor • Thus testable!
  80. Make biggest gains without Unit Testing

  81. It turned out...

  82. Quite a few crashes were caused by...

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

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

  85. FragmentTransaction • FragmentTransaction#commit • DialogFragment#show

  86. FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed =

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

    false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }
  88. Don't commit after... • AsyncTask#onPostExecute • LoaderCallbacks#onLoadFinished • Actually ANY

    KINDS OF ASYNC CALLBACKS are not safe
  89. RxLifecycle?

  90. RxLifecycle • RxLifecycle IS GREAT!! • Not all programmers, nor

    projects always need this. • Vulture is for you!
  91. Vulture • https://github.com/srym/vulture • Handles async callback safely • Based

    on PauseHandler* picture from wikimedia.org * https://goo.gl/g6w3CS
  92. Vulture void fetchAsynchronously() { /* do heavy asynchronous task here

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

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

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

    */ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } 6OTBGF
  96. Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()

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

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

    { SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
  99. Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override

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

    protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }
  101. Supported Types • Primitive types and its boxed types •

    String, Bundle, Parcelable, Serializable • ParcelableArray • ParcelableArrayList
  102. Vulture • Currently Beta release • I need your feedbacks!

    picture from wikimedia.org
  103. Thank You!

  104. Special Thanks • Illustrations by ͍Β͢ͱ΍ • http://www.irasutoya.com/ • Thank

    you so much!!! • https://www.wikimedia.org/ • All the audiences! Thank you!