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

Android From The Trenches

Android From The Trenches

These slides are for the talk "Android From the Trenches: Lessons learned from having two apps in the top 100 on Google Play for 3 years". You can watch a recording of this talk here: http://vimeo.com/92773940

Daf1617c9a4ff129239e922e8c56af1b?s=128

Donn Felker

October 18, 2014
Tweet

Transcript

  1. Android From The Trenches Lessons learned from having two apps

    in the top free category on Google play for over 3 years.
  2. @donnfelker donnfelker.com

  3. None
  4. Two Apps In The Top 100

  5. None
  6. What happens when your app gets into the top free

    category on Google Play?
  7. Quite a few things ...

  8. Here’s what should happen.

  9. Excitement!

  10. Joy!

  11. Got Monetization?

  12. Then let it rain.

  13. None
  14. None
  15. Kinda. Sometimes. Maybe Not.

  16. Here’s what really happens.

  17. Excitement!

  18. Joy!

  19. None
  20. None
  21. BUT THEN ...

  22. All Hell Breaks Loose

  23. Tons of Email Complaints, Crashes, Support Emails, Oh My!

  24. What are these crashes? Huh? These never happened in dev.

    java.io.IOException: Attempted read from closed stream. com.android.music.sync.common.SoftSyncException: java.io.IOException: Attempted read from closed stream. at com.android.music.sync.google.MusicSyncAdapter.getChangesFromServerAsDom(MusicSyncAdapter.java:545) at com.android.music.sync.google.MusicSyncAdapter.fetchDataFromServer(MusicSyncAdapter.java:488) at com.android.music.sync.common.AbstractSyncAdapter.download(AbstractSyncAdapter.java:417) at com.android.music.sync.common.AbstractSyncAdapter.innerPerformSync(AbstractSyncAdapter.java:313) at com.android.music.sync.common.AbstractSyncAdapter.onPerformLoggedSync(AbstractSyncAdapter.java:243) at com.google.android.common.LoggingThreadedSyncAdapter.onPerformSync(LoggingThreadedSyncAdapter.java:33) at android.content.AbstractThreadedSyncAdapter$SyncThread.run(AbstractThreadedSyncAdapter.java:164) Caused by: java.io.IOException: Attempted read from closed stream. at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:148) at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:159) at java.util.zip.GZIPInputStream.readFully(GZIPInputStream.java:212) at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:81) at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:64) at android.net.http.AndroidHttpClient.getUngzippedContent(AndroidHttpClient.java:218) at com.android.music.sync.api.MusicApiClientImpl.createAndExecuteMethod(MusicApiClientImpl.java:312) at com.android.music.sync.api.MusicApiClientImpl.getItems(MusicApiClientImpl.java:588) at com.android.music.sync.api.MusicApiClientImpl.getTracks(MusicApiClientImpl.java:638) at com.android.music.sync.google.MusicSyncAdapter.getChangesFromServerAsDom(MusicSyncAdapter.java:512) ... 6 more
  25. How to mitigate? Be proactive.

  26. Unit Testing With JUNit, Robolectric & Mockito

  27. Whoa Whoa Whoa I didnt say it was perfect

  28. Example @RunWith(RobolectricTestRunner.class) public class SomeValueServiceImplTest { UserValueService uvs; UserService user;

    @Before public void setup() { user = mock(UserService.class); // An interface when(user.getGoals()).thenReturn(mock(UserGoals.class)); // Mock another interface uvs = new UserValueService(user); } @Test public void shouldBeAbleToGetBasicWeightHeightInfo() { when(user.getGoals().getHeight()).thenReturn(1223.0012f); when(user.getGoals().getWeight()).thenReturn("1,223g"); assertThat(uvs.getHeightGoal()).isEqualTo(1223.0012f); assertThat(uvs.getWeightGoal()).isEqualTo("1,223g"); } }
  29. What Is this? assertThat(something) .isEqualTo(someOtherValue); Fest for Assertions & Fest

    For Android More Info: github.com/square/fest-android
  30. Espresso Googles Testing tools for Android

  31. Click on a spinner to open the item selection onView(withId(R.id.spinner_simple)).perform(click());

    Click on the item 'Americano' onData(allOf(is(instanceOf(String.class)), is("Americano"))) .perform(click()); Verify that the TextView contains the String "Americano" onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
  32. Automate It

  33. ! For Android Run Monkey against your App & Devices

  34. adb -d shell monkey -p com.instagram.android -v 2000

  35. None
  36. Automated Acceptance Testing

  37. I GOT 99 Devices and the problem is every single

    one
  38. Testing on Multiple Devices Sucks

  39. You Know Your Android Dev If your Desk Looks Like

    This
  40. Device Lab devicelab.vanamco.com

  41. Device Lab Yeah buddy

  42. Device Lab Makes it Better But Testing on Multiple Devices

    Still Sucks
  43. ADB and Spoon To The Rescue

  44. Automated Distributed Instrumentation Testing

  45. None
  46. None
  47. Cant Catch Everything Even Parachutes Have Holes

  48. This Still Happens

  49. None
  50. Log uncaught & caught exceptions

  51. None
  52. None
  53. None
  54. What about development vs production crashes and getting them confused?

  55. Use build variants in gradle

  56. build.gradle buildTypes { debug { packageNameSuffix '.debug' } release {

    proguardFile plugin.getDefaultDexGuardFile('dexguard-release.pro') proguardFile 'dexguard-project.txt' } }
  57. None
  58. NO This was not a commercial or Ad for Crashlytics

  59. Lesson Watch Your Crashes

  60. In App Monitoring Made Easy

  61. None
  62. Tons of Email

  63. Managing Email Load

  64. Case Management Auto Assignment Email Workflow and more

  65. None
  66. None
  67. TOOLS ZenDesk Desk.com Freshdesk UserVoice ...

  68. Defensive Coding

  69. Trying To Prevent

  70. Null Pointer Exception AKA - NPE

  71. NPE is your new BFF

  72. What Shouldn't be null, will be. Thanks, Murph

  73. SIMPLE EXAMPLE @Override public void onListItemClick(final ListView l, final View

    v, final int position, final long id) { super.onListItemClick(l, v, position, id); Toast.makeText(getActivity(), "Seems Legit!", Toast.LENGTH_SHORT).show(); } Seems Legit?
  74. PFFT, I WISH

  75. In Fragments We Have getActivity() Make sure you always assume

    its null
  76. Easy to fix NPE crashes occur in Every Production Android

    Application
  77. Lesson Always Check For Null Because at scale, it will

    be null Even when it should NEVER be null
  78. Software Patterns Keeping you sane and healthy

  79. Boss Man Says The app has to have the feature

    this release. I know you just heard about it and we're shipping this afternoon.
  80. Making a quick & easy update Done! Commit!

  81. None
  82. Boss Man: Happy Your Teammates though?

  83. Seriously?

  84. Spaghetti Mmmmmmmm tasty

  85. Why?

  86. A house built upon a cardboard foundation is bound to

    eventually fall
  87. App Architecture Matters

  88. Software Patterns

  89. Dependency Injection

  90. Break Dependencies

  91. None
  92. Use Dagger

  93. Compile Time Code Generation Limited Reflection

  94. Using Dagger

  95. Android Field Injection

  96. Example Android Injection From Android Bootstrap public class NewsListFragment extends

    ItemListFragment<News> { @Inject protected BootstrapServiceProvider serviceProvider; @Inject protected LogoutService logoutService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Injector.inject(this); } }
  97. POJO Constructor Injection GOOD

  98. Example Pojo CTOR Injection From Android Bootstrap public class LogoutService

    { protected final Context context; protected final AccountManager accountManager; @Inject public LogoutService(final Context context, final AccountManager accountManager) { this.context = context; this.accountManager = accountManager; } }
  99. Start This Today Instant Reward Flexibiity, composition, testing

  100. Dagger Deep Dive https://speakerdeck.com/jakewharton/android-apps-with-dagger

  101. How Do You Perform In App Communication?

  102. I Sure hope its not statics

  103. Pub Sub

  104. For Goodness Sake Use an Event Bus

  105. None
  106. None
  107. None
  108. Easily communicate inside of an app Can over complicate the

    app though Tooling Helps
  109. Popular Frameworks Otto Green Robot Event Bus RxJava

  110. Well This Looks Like Fun 04-19 10:15:14.164 464-477/system_process D/BluetoothManagerService Stored

    bluetooth Name=,Address=22:22:67:56:65:B2 04-19 10:15:14.164 464-477/system_process I/InputManager Starting input manager 04-19 10:15:14.164 464-477/system_process I/SystemServer Bluetooth Manager Service 04-19 10:15:14.172 464-477/system_process I/SystemServer Input Method Service 04-19 10:15:14.172 464-477/system_process W/InputMethodManagerService Couldnt create dir.: /data/system/inputmethod 04-19 10:15:14.184 464-477/system_process W/ResourceType Failure getting entry for 0x7f060000 (t=5 e=0) in package 0 (error -75) 04-19 10:15:14.188 464-477/system_process E/Trace error opening trace file: No such file or directory (2) 04-19 10:15:14.188 464-477/system_process I/SystemServer Accessibility Manager 04-19 10:15:14.192 484-486/? D/SurfaceFlinger setOrientation, mFbdev=0xb8969e90, mFbDev->setOrientation=0xb6774e20, orientation=0 04-19 10:15:14.192 484-486/? I/gralloc_vbox86 setOrientation: orientation=0 04-19 10:15:14.192 464-477/system_process I/SystemServer Mount Servic 04-19 10:15:14.196 485-485/? E/WVMExtractor Failed to open libwvm.so 04-19 10:15:14.196 464-477/system_process I/SystemServer LockSettingsService 04-19 10:15:14.196 464-477/system_process I/SystemServer Device Policy 04-19 10:15:14.196 464-477/system_process I/SystemServer Status Bar 04-19 10:15:14.196 464-477/system_process I/SystemServer Clipboard Service 04-19 10:15:14.196 464-477/system_process I/SystemServer NetworkManagement Service 04-19 10:15:14.196 464-477/system_process I/SystemServer Text Service Manager Service 04-19 10:15:14.204 464-477/system_process I/SystemServer NetworkStats Service 04-19 10:15:14.204 464-477/system_process I/SystemServer NetworkPolicy Service 04-19 10:15:14.208 464-524/system_process E/EventHub could not get driver version for /dev/input/mouse3, Not a typewriter 04-19 10:15:14.208 464-537/system_process I/PackageManager No secure containers on sdcard 04-19 10:15:14.208 464-537/system_process E/MountService Error processing initial volume state java.lang.NullPointerException at com.android.server.MountService.updatePublicVolumeState(MountService.java:630) at com.android.server.MountService.access$1400(MountService.java:103) at com.android.server.MountService$3.run(MountService.java:723) 04-19 10:15:14.208 464-477/system_process I/SystemServer Wi-Fi P2pService
  111. Android Logs Are Terrible

  112. Log.wtf No, seriously, this really exists in Android

  113. Yup, Here It Is

  114. But Really, Here's the Issue Log.e(tag, ...) Log.d(tag, ...) Log.i(tag,

    ...) Log.v(tag, ...)
  115. I Like Line #s

  116. Like This 04-19 10:21:13.527 1152-1152/ com.myfitnesspal.android D/ COM.MYFITNESSPAL.ANDROID/ AbstractNavigationHelper.java:370 MFP

    main starting activity normally: Intent { act=android.intent.action.MAIN flg=0x16000000 cmp=com.myfitnesspal.android/ com.myfitnesspal.activity.Welcome2 }
  117. The Ln Logger From RoboGuice/ Android Bootstrap

  118. Forget the tags ! Ln.d("Hi") Ln.e(err, "Oh No!") Ln.i("Yo") Ln.v("Foo")

  119. Ln.java Provides » File Names » Line Numbers » Package

    Name » Default Verbose logging for debug builds » Built in String formatting
  120. Hugo Annotation-Triggered method call logging for your debug builds @DebugLog

    public String getName(String first, String last) { SystemClock.sleep(15); // Don't ever really do this! return first + " " + last; } Result D/Example: ⇢ getName(first="Bill", last="Cosby") D/Example: ⇠ getName [16ms] = "Bill Cosby"
  121. Hugo https://github.com/JakeWharton/hugo

  122. Deep Links

  123. Deep Links Built Properly Open up Business opportunities and growth

  124. What is a Deep Link? A URI your app intercepts

    and responds to I'm sure you've seen this happen with Twitter, YouTube, GooglePlus, Groupon and more
  125. What does one look like? URL BASED http://www.donnfelker.com/some/path CUSTOM donnfelker://path/to/something

  126. Defined in the AndroidManifest.xml <activity android:name="com.donnfelker.activity.DeepLinkRouterActivity" android:noHistory="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/>

    <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="donnfelker"/> <data android:host="www.donnfelker.com" android:scheme="http" path="..."/> </intent-filter> </activity>
  127. Deep Links Drive Engagement

  128. Lets Talk About Fragments For a Minute

  129. FRAGMENTS You need them, even if you think you dont.

  130. Fragments are Good For » Responsive Layout » Screen and

    UX Composition » Hosting Fragments in Fragments » View Pagers » Sliding Drawer/etc
  131. Start with Fragments or Migrate Now. Waiting until later is

    expensive
  132. Do you even Build, Bro?

  133. Continuous Integration

  134. A core component For any team

  135. Options » Jenkins » TeamCity » Travis » and a

    bunch more ...
  136. Most Common Jenkins In House Travis Open Source

  137. What gets Measured Gets Managed

  138. Analytics & A/B Testing

  139. Google Analytics Localytics MixPanel Custom

  140. A/B Testing on Mobile?

  141. Very Similar to Other A/B Test Frameworks { "abtests": [

    { name: "201404-loginButtonColor", variants : [ { name: "orange", data: { color: "#FF9900" } }, { name: "yellow", data: { color: "#FEFEDF" } } ] } ] }
  142. Yeah, its custom

  143. Commercial Solutions - Apptimize.com - Arise.io - LeanPlum.com - AppIterate.com

    - SplitForce.com - Vessel - and more ...
  144. Community

  145. Embrace Open Source

  146. Libraries - Dagger - Otto - Picasso - OkHttp -

    Retrofit - Spoon - Http-Request - RxJava - Gson - Jackson - Guava - Butterknife - Hugo
  147. Libraries Continued - Timber - Madge - Scapel - StaggeredGrid

  148. Bootstrap An App & Learning

  149. Three Great Options Android Bootstrap Android Kickstartr Jake Wharton's u2020

  150. Android Bootstrap

  151. Android Kickstartr

  152. Jake Wharton's u2020

  153. Read Great Code One of the best ways to learn

  154. Thats a Wrap

  155. Lessons Learned

  156. 1. Prepare for Things to break

  157. 2. Prepare to Support Your Customers

  158. 3. Remind yourself to Stay Foolish and Stay Hungry

  159. Thank You Reach me at @donnfelker