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.3k
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
150
AWS Device FarmとCircleCIでAndroidのUIテストを自動化しよう
srym
1
5.2k
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.9k
はやい・やすい・うまい!スタートアップでも使える Retrofit + RxJava で瞬間APIクッキングレシピ
srym
2
640
I/O 2017 Short Report
srym
0
320
Other Decks in Technology
See All in Technology
データプラットフォーム技術におけるメダリオンアーキテクチャという考え方/DataPlatformWithMedallionArchitecture
smdmts
5
570
JSX - 歴史を振り返り、⾯⽩がって、エモくなろう
pal4de
3
1.1k
IIWレポートからみるID業界で話題のMCP
fujie
0
740
25分で解説する「最小権限の原則」を実現するための AWS「ポリシー」大全
opelab
9
2.2k
Oracle Cloud Infrastructure:2025年6月度サービス・アップデート
oracle4engineer
PRO
2
140
In Praise of "Normal" Engineers (LDX3)
charity
3
1.2k
実践! AIエージェント導入記
1mono2prod
0
150
Observability в PHP без боли. Олег Мифле, тимлид Altenar
lamodatech
0
300
20250623 Findy Lunch LT Brown
3150
0
790
ObsidianをMCP連携させてみる
ttnyt8701
2
140
プロダクトエンジニアリング組織への歩み、その現在地 / Our journey to becoming a product engineering organization
hiro_torii
0
110
Uniadex__公開版_20250617-AIxIoTビジネス共創ラボ_ツナガルチカラ_.pdf
iotcomjpadmin
0
150
Featured
See All Featured
KATA
mclloyd
29
14k
What's in a price? How to price your products and services
michaelherold
245
12k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
How to Ace a Technical Interview
jacobian
277
23k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
8
660
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
130
19k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Building Adaptive Systems
keathley
43
2.6k
Site-Speed That Sticks
csswizardry
10
650
Testing 201, or: Great Expectations
jmmastey
42
7.5k
StorybookのUI Testing Handbookを読んだ
zakiyama
30
5.8k
Done Done
chrislema
184
16k
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!