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
130
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
620
I/O 2017 Short Report
srym
0
310
Other Decks in Technology
See All in Technology
Oracle Database Technology Night #87-1 : Exadata Database Service on Exascale Infrastructure(ExaDB-XS)サービス詳細
oracle4engineer
PRO
1
170
2/18 Making Security Scale: メルカリが考えるセキュリティ戦略 - Coincheck x LayerX x Mercari
jsonf
0
190
Share my, our lessons from the road to re:Invent
naospon
0
140
ウォンテッドリーのデータパイプラインを支える ETL のための analytics, rds-exporter / analytics, rds-exporter for ETL to support Wantedly's data pipeline
unblee
0
120
EMConf JP 2025 懇親会LT / EMConf JP 2025 social gathering
sugamasao
2
190
Raycast Favorites × Script Command で実現するお手軽情報チェック
smasato
1
150
アジャイルな開発チームでテスト戦略の話は誰がする? / Who Talks About Test Strategy?
ak1210
1
550
Active Directory攻防
cryptopeg
PRO
8
5.4k
わたしがEMとして入社した「最初の100日」の過ごし方 / EMConfJp2025
daiksy
14
4.9k
【内製開発Summit 2025】イオンスマートテクノロジーの内製化組織の作り方/In-house-development-summit-AST
aeonpeople
2
620
Amazon Aurora のバージョンアップ手法について
smt7174
2
140
AIエージェント入門
minorun365
PRO
31
17k
Featured
See All Featured
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.4k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
32
2.1k
For a Future-Friendly Web
brad_frost
176
9.6k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
45
9.4k
GitHub's CSS Performance
jonrohan
1030
460k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
27
1.9k
Six Lessons from altMBA
skipperchong
27
3.6k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Building an army of robots
kneath
303
45k
Rails Girls Zürich Keynote
gr2m
94
13k
StorybookのUI Testing Handbookを読んだ
zakiyama
28
5.5k
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!