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%」

Fumihiko Shiroyama

March 09, 2017
Tweet

More Decks by Fumihiko Shiroyama

Other Decks in Technology

Transcript

  1. Journey to
    99% Crash Free
    Fumihiko Shiroyama

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. The Fact Your App is Crashing

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. Keep Going!!!

    View Slide

  11. View Slide

  12. Fabric
    • Crashlytics
    • Beta
    • Answers
    • Acquired by Google

    View Slide

  13. "41MVHJO

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. Firebase Crash Reporting
    • Similar to Crashlytics

    View Slide

  18. "41MVHJO

    View Slide

  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

    View Slide

  20. Crash Reporting
    Normal Stack Trace

    View Slide

  21. Crash Reporting
    Device Info

    View Slide

  22. Crash Reporting
    Contextual History

    View Slide

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

    View Slide

  24. Integration

    View Slide

  25. View Slide

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

    View Slide

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

    View Slide

  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);
    }
    }
    }

    View Slide

  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);
    }
    }
    }

    View Slide

  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);
    }
    }
    }

    View Slide

  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

    View Slide

  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());
    }
    }
    }

    View Slide

  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());
    }
    }
    }

    View Slide

  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());
    }
    }
    }

    View Slide

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

    View Slide

  36. Take the first step for Unit Testing

    View Slide

  37. Why can't you write test?

    View Slide

  38. Typical Case

    View Slide

  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);
    }
    });
    }

    View Slide

  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);
    }
    });
    }

    View Slide

  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);
    }
    });
    }

    View Slide

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

    View Slide

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

    View Slide

  44. How do I test MyActivity#fetchUser ?

    View Slide

  45. That's impossible

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  55. Anything else?

    View Slide

  56. Problem is Activity

    View Slide

  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);
    }
    });
    }

    View Slide

  58. Let's split this into parts!

    View Slide

  59. MVP

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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*

    View Slide

  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) {
    }
    }

    View Slide

  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) {
    }
    }

    View Slide

  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) {
    }
    }

    View Slide

  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);
    }
    });
    }

    View Slide

  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);
    }
    });
    }

    View Slide

  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);
    }
    });
    }

    View Slide

  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) {}
    }

    View Slide

  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) {}
    }

    View Slide

  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()); }
    }

    View Slide

  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()); }
    }

    View Slide

  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()); }
    }

    View Slide

  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()); }
    }

    View Slide

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

    View Slide

  80. Make biggest gains
    without Unit Testing

    View Slide

  81. It turned out...

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  85. FragmentTransaction
    • FragmentTransaction#commit
    • DialogFragment#show

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  89. RxLifecycle?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  103. Thank You!

    View Slide

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

    View Slide