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

Donn Felker

October 18, 2014
Tweet

More Decks by Donn Felker

Other Decks in Technology

Transcript

  1. Android From The Trenches
    Lessons learned from having two apps in the top
    free category on Google play for over 3 years.

    View full-size slide

  2. @donnfelker
    donnfelker.com

    View full-size slide

  3. Two Apps
    In The Top 100

    View full-size slide

  4. What happens
    when your app
    gets into the
    top free
    category on
    Google Play?

    View full-size slide

  5. Quite a few things ...

    View full-size slide

  6. Here’s what should happen.

    View full-size slide

  7. Got Monetization?

    View full-size slide

  8. Then let it rain.

    View full-size slide

  9. Kinda. Sometimes. Maybe Not.

    View full-size slide

  10. Here’s what really happens.

    View full-size slide

  11. BUT THEN ...

    View full-size slide

  12. All Hell Breaks
    Loose

    View full-size slide

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

    View full-size slide

  14. 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.(GZIPInputStream.java:81)
    at java.util.zip.GZIPInputStream.(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

    View full-size slide

  15. How to mitigate?
    Be proactive.

    View full-size slide

  16. Unit Testing With JUNit,
    Robolectric & Mockito

    View full-size slide

  17. Whoa Whoa Whoa
    I didnt say it was perfect

    View full-size slide

  18. 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");
    }
    }

    View full-size slide

  19. What Is this?
    assertThat(something)
    .isEqualTo(someOtherValue);
    Fest for Assertions & Fest
    For Android
    More Info: github.com/square/fest-android

    View full-size slide

  20. Espresso
    Googles Testing tools for Android

    View full-size slide

  21. 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"))));

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  24. Automated
    Acceptance Testing

    View full-size slide

  25. I GOT 99 Devices
    and the problem is every single one

    View full-size slide

  26. Testing on
    Multiple Devices
    Sucks

    View full-size slide

  27. You Know Your Android Dev
    If your Desk Looks Like This

    View full-size slide

  28. Device Lab
    devicelab.vanamco.com

    View full-size slide

  29. Device Lab
    Yeah buddy

    View full-size slide

  30. Device Lab Makes it Better
    But Testing on Multiple Devices Still Sucks

    View full-size slide

  31. ADB and Spoon To The Rescue

    View full-size slide

  32. Automated Distributed
    Instrumentation Testing

    View full-size slide

  33. Cant Catch Everything
    Even Parachutes Have Holes

    View full-size slide

  34. This Still
    Happens

    View full-size slide

  35. Log uncaught &
    caught
    exceptions

    View full-size slide

  36. What about
    development vs
    production crashes
    and getting
    them confused?

    View full-size slide

  37. Use build variants in gradle

    View full-size slide

  38. build.gradle
    buildTypes {
    debug {
    packageNameSuffix '.debug'
    }
    release {
    proguardFile plugin.getDefaultDexGuardFile('dexguard-release.pro')
    proguardFile 'dexguard-project.txt'
    }
    }

    View full-size slide

  39. NO This was not
    a commercial or Ad for Crashlytics

    View full-size slide

  40. Lesson
    Watch Your Crashes

    View full-size slide

  41. In App Monitoring
    Made Easy

    View full-size slide

  42. Tons of Email

    View full-size slide

  43. Managing Email Load

    View full-size slide

  44. Case
    Management
    Auto
    Assignment
    Email Workflow
    and more

    View full-size slide

  45. TOOLS
    ZenDesk
    Desk.com
    Freshdesk
    UserVoice
    ...

    View full-size slide

  46. Defensive Coding

    View full-size slide

  47. Trying To
    Prevent

    View full-size slide

  48. Null Pointer Exception
    AKA - NPE

    View full-size slide

  49. NPE is your new
    BFF

    View full-size slide

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

    View full-size slide

  51. 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?

    View full-size slide

  52. PFFT, I WISH

    View full-size slide

  53. In Fragments We Have
    getActivity()
    Make sure you always
    assume its null

    View full-size slide

  54. Easy to fix NPE
    crashes occur
    in Every
    Production
    Android
    Application

    View full-size slide

  55. Lesson
    Always Check For Null
    Because at scale, it will be null
    Even when it should NEVER be null

    View full-size slide

  56. Software Patterns
    Keeping you sane and healthy

    View full-size slide

  57. 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.

    View full-size slide

  58. Making a quick &
    easy update
    Done!
    Commit!

    View full-size slide

  59. Boss Man: Happy
    Your Teammates though?

    View full-size slide

  60. Spaghetti
    Mmmmmmmm tasty

    View full-size slide

  61. A house built
    upon a
    cardboard
    foundation is
    bound to
    eventually fall

    View full-size slide

  62. App Architecture
    Matters

    View full-size slide

  63. Software
    Patterns

    View full-size slide

  64. Dependency
    Injection

    View full-size slide

  65. Break
    Dependencies

    View full-size slide

  66. Compile Time
    Code Generation
    Limited Reflection

    View full-size slide

  67. Android
    Field Injection

    View full-size slide

  68. Example Android Injection
    From Android Bootstrap
    public class NewsListFragment extends ItemListFragment {
    @Inject protected BootstrapServiceProvider serviceProvider;
    @Inject protected LogoutService logoutService;
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Injector.inject(this);
    }
    }

    View full-size slide

  69. POJO Constructor Injection
    GOOD

    View full-size slide

  70. 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;
    }
    }

    View full-size slide

  71. Start This Today
    Instant Reward
    Flexibiity, composition, testing

    View full-size slide

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

    View full-size slide

  73. How Do You Perform
    In App Communication?

    View full-size slide

  74. I Sure hope its
    not statics

    View full-size slide

  75. For Goodness Sake
    Use an Event Bus

    View full-size slide

  76. Easily
    communicate
    inside of an app
    Can over complicate
    the app though
    Tooling Helps

    View full-size slide

  77. Popular Frameworks
    Otto
    Green Robot Event Bus
    RxJava

    View full-size slide

  78. 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

    View full-size slide

  79. Android Logs
    Are Terrible

    View full-size slide

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

    View full-size slide

  81. Yup, Here It Is

    View full-size slide

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

    View full-size slide

  83. I Like Line #s

    View full-size slide

  84. 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 }

    View full-size slide

  85. The Ln Logger
    From RoboGuice/
    Android Bootstrap

    View full-size slide

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

    View full-size slide

  87. Ln.java Provides
    » File Names
    » Line Numbers
    » Package Name
    » Default Verbose logging for debug
    builds
    » Built in String formatting

    View full-size slide

  88. 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"

    View full-size slide

  89. Hugo
    https://github.com/JakeWharton/hugo

    View full-size slide

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

    View full-size slide

  91. 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

    View full-size slide

  92. What does one look like?
    URL BASED
    http://www.donnfelker.com/some/path
    CUSTOM
    donnfelker://path/to/something

    View full-size slide

  93. Defined in the AndroidManifest.xml
    android:name="com.donnfelker.activity.DeepLinkRouterActivity"
    android:noHistory="true">








    View full-size slide

  94. Deep Links
    Drive Engagement

    View full-size slide

  95. Lets Talk About
    Fragments
    For a Minute

    View full-size slide

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

    View full-size slide

  97. Fragments are Good For
    » Responsive Layout
    » Screen and UX Composition
    » Hosting Fragments in Fragments
    » View Pagers
    » Sliding Drawer/etc

    View full-size slide

  98. Start with
    Fragments or
    Migrate Now.
    Waiting until
    later is
    expensive

    View full-size slide

  99. Do you even
    Build, Bro?

    View full-size slide

  100. Continuous
    Integration

    View full-size slide

  101. A core component
    For any team

    View full-size slide

  102. Options
    » Jenkins
    » TeamCity
    » Travis
    » and a bunch more ...

    View full-size slide

  103. Most Common
    Jenkins In House
    Travis Open Source

    View full-size slide

  104. What gets Measured
    Gets Managed

    View full-size slide

  105. Analytics &
    A/B Testing

    View full-size slide

  106. Google Analytics
    Localytics
    MixPanel
    Custom

    View full-size slide

  107. A/B Testing
    on Mobile?

    View full-size slide

  108. Very Similar to Other A/B
    Test Frameworks
    {
    "abtests":
    [
    {
    name: "201404-loginButtonColor",
    variants : [
    {
    name: "orange",
    data:
    {
    color: "#FF9900"
    }
    },
    {
    name: "yellow",
    data:
    {
    color: "#FEFEDF"
    }
    }
    ]
    }
    ]
    }

    View full-size slide

  109. Yeah, its custom

    View full-size slide

  110. Commercial Solutions
    - Apptimize.com
    - Arise.io
    - LeanPlum.com
    - AppIterate.com
    - SplitForce.com
    - Vessel
    - and more ...

    View full-size slide

  111. Embrace
    Open Source

    View full-size slide

  112. Libraries
    - Dagger
    - Otto
    - Picasso
    - OkHttp
    - Retrofit
    - Spoon
    - Http-Request
    - RxJava
    - Gson
    - Jackson
    - Guava
    - Butterknife
    - Hugo

    View full-size slide

  113. Libraries Continued
    - Timber
    - Madge
    - Scapel
    - StaggeredGrid

    View full-size slide

  114. Bootstrap An App
    & Learning

    View full-size slide

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

    View full-size slide

  116. Android Bootstrap

    View full-size slide

  117. Android Kickstartr

    View full-size slide

  118. Jake
    Wharton's
    u2020

    View full-size slide

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

    View full-size slide

  120. Thats a Wrap

    View full-size slide

  121. Lessons Learned

    View full-size slide

  122. 1. Prepare for
    Things to break

    View full-size slide

  123. 2. Prepare to
    Support Your Customers

    View full-size slide

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

    View full-size slide

  125. Thank You
    Reach me at @donnfelker

    View full-size slide