Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Journey to 99% Crash Free
Search
Fumihiko Shiroyama
March 09, 2017
Technology
5
4.2k
Journey to 99% Crash Free
DroidKaigi 2017 Day 2 (17:10 - 17:40 Room 1)
「テスト0から目指すクラッシュフリー率99%」
Fumihiko Shiroyama
March 09, 2017
Tweet
Share
More Decks by Fumihiko Shiroyama
See All by Fumihiko Shiroyama
The world of Android wireless communications without Internet
srym
1
140
AWS Device FarmとCircleCIでAndroidのUIテストを自動化しよう
srym
1
5.1k
Spring BootをKotlinで作成しAmazon Elastic Container Service (ECS) で稼働させる
srym
6
2k
iOSDC_2019_DeviceFarm.pdf
srym
8
20k
世界で戦うエンジニアになるために_公開用.pdf
srym
18
45k
Unit Testing in a Nutshell - DroidKaigi 2018
srym
11
15k
Clean Architecture & TDD
srym
1
3.8k
はやい・やすい・うまい!スタートアップでも使える Retrofit + RxJava で瞬間APIクッキングレシピ
srym
2
630
I/O 2017 Short Report
srym
0
310
Other Decks in Technology
See All in Technology
newmo の創業を支える Software Architecture と Platform Engineering
110y
5
600
テスト設計、逆から読むとおもしろい──仕様にない“望ましさ”の逆設計
mhlyc
0
190
UIパフォーマンス最適化: AIを活用して100倍の速度向上を実現した事例
kinocoboy2
1
670
地に足の付いた現実的な技術選定から魔力のある体験を得る『AIレシート読み取り機能』のケーススタディ / From Grounded Tech Choices to Magical UX: A Case Study of AI Receipt Scanning
moznion
5
2k
Creative UIs with Compose: Kotlinconf 2025
chrishorner
0
120
encoding/json v2を予習しよう!
yuyu_hf
PRO
1
220
AIエージェントのオブザーバビリティについて
yunosukey
1
390
AIフレンドリーなプロダクト開発を目指して 〜MCPを橋渡しにした環境移行〜
shinpr
0
140
GPU 클라우드 환경에서의 회복탄력적 AI 운영 : 훈련 및 추론을 위한 견고한 아키텍처와 전략
inureyes
PRO
0
140
NAB Show 2025 動画技術関連レポート / NAB Show 2025 Report
cyberagentdevelopers
PRO
0
130
Ruby on Rails の楽しみ方
morihirok
6
3.2k
スイッチのBMC、つかってますか?
sonic
0
440
Featured
See All Featured
The Language of Interfaces
destraynor
158
25k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
45
7.3k
Being A Developer After 40
akosma
91
590k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
60k
Reflections from 52 weeks, 52 projects
jeffersonlam
349
20k
The Pragmatic Product Professional
lauravandoore
33
6.6k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
30
2.4k
How to Ace a Technical Interview
jacobian
276
23k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
31
1.2k
Agile that works and the tools we love
rasmusluckow
329
21k
Music & Morning Musume
bryan
47
6.5k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Transcript
Journey to 99% Crash Free Fumihiko Shiroyama
Slide URL • https://goo.gl/zwxdyy
About Me • Fumihiko Shiroyama • Android App Developer •
Nikkei Inc. • https://github.com/srym • https://twitter.com/fushiroyama
Agenda • Face up to the reality of crash! •
Take the first step for Unit Testing • Make biggest gains without Unit Testing
The Fact Your App is Crashing
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000 $SBTIFT
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000 • 99.9% Crash Free? Great!!
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000 • 99.9% Crash Free? Great!! 4UJMM$SBTIFT
Keep Going!!!
None
Fabric • Crashlytics • Beta • Answers • Acquired by
Google
"41MVHJO
Crashlytics • Automatically collects crash info • No need for
uploading obfuscation mappings • Crashlytics#logException(e) for non-fatal
Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345");
Crashlytics.setUserEmail("
[email protected]
"); Crashlytics.setUserName("Test User"); }
Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345");
Crashlytics.setUserEmail("
[email protected]
"); Crashlytics.setUserName("Test User"); }
Firebase Crash Reporting • Similar to Crashlytics
"41MVHJO
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
Crash Reporting Normal Stack Trace
Crash Reporting Device Info
Crash Reporting Contextual History
Crash Reporting Contextual History $00- &WFOU -PH
Integration
None
Timber dependencies { compile 'com.jakewharton.timber:timber:4.5.1' }
Timber dependencies { compile 'com.jakewharton.timber:timber:4.5.1' } #FUUFS-PHE
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); } } }
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); } } }
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); } } }
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
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()); } } }
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()); } } }
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()); } } }
Timber Timber.d("log message"); // or Timber.e(error, error.getMessage());
Take the first step for Unit Testing
Why can't you write test?
Typical Case
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); } }); }
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); } }); }
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); } }); }
Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); }
Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); }
pFMEBDDFTT
How do I test MyActivity#fetchUser ?
That's impossible
Make it testable private User fetchUser(long userId) { return apiClient.findUserById(userId);
}
Make it testable private User fetchUser(ApiClient apiClient, long userId) {
return apiClient.findUserById(userId); }
private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); }
3FNPWFE pFMEBDDFTT Make it testable
private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); }
Make it testable
public class UserFetcher { private User fetchUser(ApiClient apiClient, long userId)
{ return apiClient.findUserById(userId); } } Make it testable
public class UserFetcher { public User fetchUser(ApiClient apiClient, long userId)
{ return apiClient.findUserById(userId); } } Make it testable
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
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
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
Anything else?
Problem is Activity
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); } }); }
Let's split this into parts!
MVP
What is MVP • Model - View - Presenter •
View…Activity (Fragment) • Model…Business Logic (e.g. fetching data) • Presenter…Bridge between V & M
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
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
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
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
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
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*
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) { } }
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) { } }
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) { } }
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); } }); }
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); } }); }
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); } }); }
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) {} }
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) {} }
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()); } }
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()); } }
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()); } }
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()); } }
Result • Now objects' responsibilities are clearer • Dependency is
provided at constructor • Thus testable!
Make biggest gains without Unit Testing
It turned out...
Quite a few crashes were caused by...
IllegalStateException java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
IllegalStateException java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 'SBHNFOU5SBOTBDUJPODPNNJU
FragmentTransaction • FragmentTransaction#commit • DialogFragment#show
FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed =
false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }
FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed =
false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }
Don't commit after... • AsyncTask#onPostExecute • LoaderCallbacks#onLoadFinished • Actually ANY
KINDS OF ASYNC CALLBACKS are not safe
RxLifecycle?
RxLifecycle • RxLifecycle IS GREAT!! • Not all programmers, nor
projects always need this. • Vulture is for you!
Vulture • https://github.com/srym/vulture • Handles async callback safely • Based
on PauseHandler* picture from wikimedia.org * https://goo.gl/g6w3CS
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } 6OTBGF
Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()
{ SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()
{ SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()
{ SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override
protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }
Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override
protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }
Supported Types • Primitive types and its boxed types •
String, Bundle, Parcelable, Serializable • ParcelableArray • ParcelableArrayList
Vulture • Currently Beta release • I need your feedbacks!
picture from wikimedia.org
Thank You!
Special Thanks • Illustrations by ͍Β͢ͱ • http://www.irasutoya.com/ • Thank
you so much!!! • https://www.wikimedia.org/ • All the audiences! Thank you!