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

"It's an Inside Job" - Building Debug Features

"It's an Inside Job" - Building Debug Features

Droidcon Boston - April 11, 2017

DC Android - March 15, 2017

Sam Edwards

April 11, 2017
Tweet

More Decks by Sam Edwards

Other Decks in Programming

Transcript

  1. #droidconbos @HandstandSam
    “It’s an Inside Job”
    BUILDING DEBUG FEATURES
    Sam Edwards
    @HandstandSam

    View full-size slide

  2. #droidconbos @HandstandSam
    1. WHAT IS A DEBUG FEATURE?
    2. GRADLE BUILD VARIANTS
    3. EXAMPLE SCENARIOS
    4. OPEN SOURCE INSPIRATION
    5. DON’T SHIP TO PROD
    OUTLINE

    View full-size slide

  3. #droidconbos @HandstandSam
    WHAT IS A
    DEBUG FEATURE?

    View full-size slide

  4. #droidconbos @HandstandSam
    DEBUG FEATURES
    ● HELP YOU TEST AND DEBUG
    ● DON’T SHIP TO PRODUCTION

    View full-size slide

  5. #droidconbos @HandstandSam
    AS DEVELOPERS,
    WE HAVE INSIDER ACCESS TO BUILD THEM

    View full-size slide

  6. #droidconbos @HandstandSam
    DEBUG FEATURES IN YOUR APP ENABLE YOUR
    TEAM TO EFFECTIVELY PERFORM TASKS THAT CAN’T BE
    AUTOMATED.

    View full-size slide

  7. #droidconbos @HandstandSam
    DEBUG FEATURE
    POSSIBILITY MATRIX

    View full-size slide

  8. #droidconbos @HandstandSam
    Modify Mock Monitor Capture Inspect
    Configurations
    Persistent Data
    Networking
    Resources
    Views
    Notifications
    Sensors
    … and more.

    View full-size slide

  9. #droidconbos @HandstandSam
    GRADLE BUILD VARIANTS

    View full-size slide

  10. #droidconbos @HandstandSam
    “main” SOURCE FOLDER

    View full-size slide

  11. #droidconbos @HandstandSam
    BuildConfig.java

    View full-size slide

  12. #droidconbos @HandstandSam
    DEFAULTS
    =
    =
    RELEASE.APK
    DEBUG.APK
    “main”

    View full-size slide

  13. #droidconbos @HandstandSam
    “debug” and “release” SOURCE FOLDERS

    View full-size slide

  14. #droidconbos @HandstandSam
    “debug” & “release” SOURCE FOLDERS
    =
    =
    RELEASE.APK
    DEBUG.APK
    “main”
    “debug”
    “release”
    +

    View full-size slide

  15. #droidconbos @HandstandSam
    SOURCE STRUCTURE

    View full-size slide

  16. #droidconbos @HandstandSam
    “debug”

    View full-size slide

  17. #droidconbos @HandstandSam
    “release”

    View full-size slide

  18. #droidconbos @HandstandSam
    GRADLE COMPILE DEPENDENCIES

    View full-size slide

  19. #droidconbos @HandstandSam
    GRADLE DEPENDENCIES

    View full-size slide

  20. #droidconbos @HandstandSam
    GRADLE DEPENDENCIES
    =
    =
    RELEASE.APK
    DEBUG.APK
    “compile”
    “debugCompile”
    “releaseCompile”
    +

    View full-size slide

  21. #droidconbos @HandstandSam
    GENERATED APKS

    View full-size slide

  22. #droidconbos @HandstandSam
    EXAMPLE SCENARIO

    View full-size slide

  23. #droidconbos @HandstandSam
    SAMPLE APP
    https://github.com/handstandsam/BuildingDebugFeatures

    View full-size slide

  24. #droidconbos @HandstandSam
    ANDROID
    DEV

    View full-size slide

  25. #droidconbos @HandstandSam
    ANDROID
    DEV
    DESIGN

    View full-size slide

  26. #droidconbos @HandstandSam
    ANDROID
    DEV
    DESIGN BACKEND

    View full-size slide

  27. #droidconbos @HandstandSam
    ANDROID
    DEV
    PRODUCT
    DESIGN BACKEND

    View full-size slide

  28. #droidconbos @HandstandSam
    ANDROID
    DEV
    TESTING
    PRODUCT
    DESIGN BACKEND

    View full-size slide

  29. #droidconbos @HandstandSam
    JOE: “CAN I HAVE A SPECIAL BUILD
    TO TEST A NEW SERVER ENDPOINT?”
    BACKEND

    View full-size slide

  30. #droidconbos @HandstandSam
    SURE, BUT IT’LL TAKE A FEW MINUTES.

    View full-size slide

  31. #droidconbos @HandstandSam
    @Provides
    Retrofit.Builder retrofitBuilder(OkHttpClient.Builder okHttpClientBuilder) {
    Retrofit.Builder builder = new Retrofit.Builder()
    .baseUrl("https://server")
    .addConverterFactory(MoshiConverterFactory.create(new Moshi.Builder().build()))
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClientBuilder.build());
    return builder;
    }

    View full-size slide

  32. #droidconbos @HandstandSam
    @Provides
    Retrofit.Builder retrofitBuilder(OkHttpClient.Builder okHttpClientBuilder) {
    Retrofit.Builder builder = new Retrofit.Builder()
    .baseUrl("https://temp-server")
    .addConverterFactory(MoshiConverterFactory.create(new Moshi.Builder().build()))
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClientBuilder.build());
    return builder;
    }

    View full-size slide

  33. #droidconbos @HandstandSam
    WAIT FOR GRADLE...

    View full-size slide

  34. #droidconbos @HandstandSam
    How many times have you hit ______
    In and then waited for

    View full-size slide

  35. #droidconbos @HandstandSam
    This ISN’T the first time you’ve been
    asked for this.
    BACKEND BACKEND BACKEND BACKEND BACKEND
    TESTING TESTING TESTING TESTING
    ANDROID
    DEV

    View full-size slide

  36. #droidconbos @HandstandSam
    NOR THE LAST
    BACKEND BACKEND BACKEND BACKEND BACKEND
    TESTING TESTING TESTING TESTING
    ANDROID
    DEV
    BACKEND BACKEND BACKEND BACKEND BACKEND
    TESTING TESTING TESTING TESTING
    ANDROID
    DEV
    BACKEND BACKEND BACKEND BACKEND BACKEND
    TESTING TESTING TESTING TESTING
    ANDROID
    DEV
    BACKEND BACKEND BACKEND BACKEND BACKEND
    TESTING TESTING TESTING TESTING
    ANDROID
    DEV

    View full-size slide

  37. #droidconbos @HandstandSam
    YOU CAN ENABLE OTHERS

    View full-size slide

  38. #droidconbos @HandstandSam
    CUSTOM DEBUG FEATURE
    “CHANGE ENDPOINT”

    View full-size slide

  39. #droidconbos @HandstandSam
    WHAT WILL THIS UI LOOK LIKE?

    View full-size slide

  40. #droidconbos @HandstandSam
    DEBUG DRAWER https://github.com/JakeWharton/u2020
    ● Requires changes to existing UI hierarchy.
    ● Could interfere with actions that use side-to-side
    swiping.

    View full-size slide

  41. #droidconbos @HandstandSam
    HOVERING MENU https://github.com/google/hover
    ● REQUIRES NO CODE CHANGES
    ● LOOKS COOL
    ● REQUIRES LEARNING A NEW PARADIGM OF
    USING THE WINDOW MANAGER

    View full-size slide

  42. #droidconbos @HandstandSam
    NEW ACTIVITY
    ● REQUIRES NO CHANGES TO EXISTING UI CODE
    ● STRAIGHT FORWARD

    View full-size slide

  43. #droidconbos @HandstandSam
    BUT… HOW WILL THEY ACCESS
    THE NEW ACTIVITY?

    View full-size slide

  44. #droidconbos @HandstandSam
    OPTION 1:
    MULTIPLE LAUNCHER ICONS
    FOR A SINGLE APP

    View full-size slide

  45. #droidconbos @HandstandSam
    android:name=".MainActivity"
    android:launchMode="singleTask">






    MAIN ACTIVITY (src/main)

    View full-size slide

  46. #droidconbos @HandstandSam
    android:name=".DebugActivity"
    android:icon="@drawable/magnifying_glass"
    android:label="BDF - DEBUG"
    android:launchMode="singleTask">






    DEBUG FEATURE ACTIVITY (src/debug)

    View full-size slide

  47. #droidconbos @HandstandSam
    android:name=".DebugFeatureActivity"
    android:launchMode="singleTask">






    DEBUG FEATURE ACTIVITY (src/debug)

    View full-size slide

  48. #droidconbos @HandstandSam

    View full-size slide

  49. #droidconbos @HandstandSam
    OPTION 2:
    PERSISTENT NOTIFICATION

    View full-size slide

  50. #droidconbos @HandstandSam
    HOW WILL WE SAVE AND USE
    THE NEW ENDPOINT?

    View full-size slide

  51. #droidconbos @HandstandSam
    ALLOW USER TO EDIT ENDPOINT

    View full-size slide

  52. #droidconbos @HandstandSam
    public class DebugPreferences {
    public static final String BASE_URL = "base_url";
    private final SharedPreferences sharedPreferences;
    public DebugPreferences(Context context) {
    sharedPreferences = PreferenceManager
    .getDefaultSharedPreferences(context.getApplicationContext());
    }
    public void setBaseUrl(String baseUrl) {
    sharedPreferences.edit().putString(BASE_URL, baseUrl).apply();
    }
    public String getBaseUrl() { return sharedPreferences.getString(BASE_URL, "https://server"); }
    }
    SAVE IN A PREFERENCE

    View full-size slide

  53. #droidconbos @HandstandSam
    FORCE KILL AND RESTART APP
    Process Phoenix https://github.com/JakeWharton/ProcessPhoenix

    View full-size slide

  54. #droidconbos @HandstandSam
    @Provides
    Retrofit.Builder retrofitBuilder(DebugPreferences debugPreferences,
    OkHttpClient.Builder okHttpClientBuilder) {
    Retrofit.Builder builder = new Retrofit.Builder()
    .baseUrl(debugPreferences.getBaseUrl())
    .addConverterFactory(MoshiConverterFactory.create(new Moshi.Builder().build()))
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClientBuilder.build());
    return builder;
    }
    APPLY UPON APP INITIALIZATION

    View full-size slide

  55. #droidconbos @HandstandSam
    CONFIG CHANGE TOAST

    View full-size slide

  56. #droidconbos @HandstandSam
    WICKED AWESOME

    View full-size slide

  57. #droidconbos @HandstandSam
    ANDROID
    DEV
    TESTING
    PRODUCT
    DESIGN BACKEND

    View full-size slide

  58. #droidconbos @HandstandSam
    PRODUCT
    JEFF: “I NEED AN EASY WAY TO
    DEMO NOTIFICATIONS.”

    View full-size slide

  59. #droidconbos @HandstandSam
    CUSTOM DEBUG FEATURE
    “NOTIFICATION TESTING”

    View full-size slide

  60. #droidconbos @HandstandSam
    TRIGGER A NOTIFICATION WITHOUT
    GOING THROUGH GCM?

    View full-size slide

  61. #droidconbos @HandstandSam
    Broadcast Receivers via ADB

    View full-size slide

  62. #droidconbos @HandstandSam





    BROADCAST RECEIVER (src/debug/AndroidManifest.xml)

    View full-size slide

  63. #droidconbos @HandstandSam
    public class DebugBroadcastReceiver extends BroadcastReceiver {
    private static final String PR_NOTIFICATION = "PR_NOTIFICATION";
    @Override
    public void onReceive(Context context, Intent intent) {
    String intentAction = intent.getAction();
    if (intentAction == null || !PR_NOTIFICATION.equals(intentAction)) {
    return;
    }
    Timber.d("processing " + PR_NOTIFICATION);
    Bundle extras = intent.getExtras();
    if (extras == null) {
    return;
    }
    final String username = extras.getString("username");
    Timber.d("username " + username);
    IntentUtils.triggerPRNotification(context, username);
    }
    }
    BROADCAST RECEIVER

    View full-size slide

  64. #droidconbos @HandstandSam

    View full-size slide

  65. #droidconbos @HandstandSam
    DYNAMIC
    HOME SCREEN SHORTCUTS

    View full-size slide

  66. #droidconbos @HandstandSam

    View full-size slide

  67. #droidconbos @HandstandSam
    Intent addShortcutIntent = new Intent();
    addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, deepLinkIntent);
    addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "My Shortcut");
    addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
    Intent.ShortcutIconResource.fromContext(applicationContext, R.drawable.shortcut));
    addShortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    applicationContext.sendBroadcast(addShortcutIntent);
    DYNAMIC HOME SCREEN SHORTCUTS

    View full-size slide

  68. #droidconbos @HandstandSam
    Intent addShortcutIntent = new Intent();
    addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, deepLinkIntent);
    addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "My Shortcut");
    addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
    Intent.ShortcutIconResource.fromContext(applicationContext, R.drawable.shortcut));
    addShortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    applicationContext.sendBroadcast(addShortcutIntent);
    DYNAMIC HOME SCREEN SHORTCUTS

    View full-size slide

  69. #droidconbos @HandstandSam
    WICKED AWESOME

    View full-size slide

  70. #droidconbos @HandstandSam
    ANDROID
    DEV
    TESTING
    PRODUCT
    DESIGN BACKEND

    View full-size slide

  71. #droidconbos @HandstandSam
    I WANT TO VALIDATE
    NETWORKING TRAFFIC IN A
    VISUAL WAY.
    TESTING

    View full-size slide

  72. #droidconbos @HandstandSam
    OPTION 1: STETHO
    ● CHROME DEVELOPER TOOLS
    ● NETWORK INSPECTION
    ● PREFERENCE INSPECTION & MODIFICATION
    ● DB INSPECTION & MODIFICATION
    ● VIEW HIERARCHY
    ● JAVASCRIPT CONSOLE
    ● DUMP APP

    View full-size slide

  73. #droidconbos @HandstandSam

    View full-size slide

  74. #droidconbos @HandstandSam

    View full-size slide

  75. #droidconbos @HandstandSam

    View full-size slide

  76. #droidconbos @HandstandSam
    SCREENSHOT>

    View full-size slide

  77. #droidconbos @HandstandSam
    “I’M NOT GOING TO BE
    CONNECTED TO A COMPUTER.”

    View full-size slide

  78. #droidconbos @HandstandSam
    OPTION 2: CHUCK
    https://github.com/jgilfelt/chuck

    View full-size slide

  79. #droidconbos @HandstandSam
    SURE, GIVE ME A FEW MINUTES,
    I’LL ADD IT TO ALL FUTURE DEBUG BUILDS.

    View full-size slide

  80. #droidconbos @HandstandSam
    WICKED AWESOME

    View full-size slide

  81. #droidconbos @HandstandSam
    OPEN SOURCE INSPIRATION

    View full-size slide

  82. #droidconbos @HandstandSam
    DON’T RE-INVENT THE WHEEL
    LEVERAGE EXISTING
    OPEN SOURCE LIBRARIES

    View full-size slide

  83. #droidconbos @HandstandSam
    UI DESIGN & VIEW INSPECTION

    View full-size slide

  84. #droidconbos @HandstandSam
    Keyline Pushing https://play.google.com/store/apps/details?id=com.faizmalkani.keylines

    Overlays Material Design Keylines

    View full-size slide

  85. #droidconbos @HandstandSam
    Scalpel https://github.com/JakeWharton/scalpel

    A surgical debugging tool to uncover the
    layers under your app.

    View full-size slide

  86. #droidconbos @HandstandSam
    Madge https://github.com/JakeWharton/madge

    Overlays for debugging whether assets are drawing at their native
    resolution.

    View full-size slide

  87. #droidconbos @HandstandSam
    Logging

    View full-size slide

  88. #droidconbos @HandstandSam
    Logcat

    View full-size slide

  89. #droidconbos @HandstandSam
    Timber https://github.com/JakeWharton/timber

    Abstraction on top of Android Log

    Log.d(TAG, message);

    More concise syntax

    Timber.d(message);

    d(message); - Static Import

    Ability to toggle logging on/off.

    Ability to keep logs in memory.

    View full-size slide

  90. #droidconbos @HandstandSam
    Hugo https://github.com/JakeWharton/hugo

    Annotation-triggered method call logging for your debug builds.

    View full-size slide

  91. #droidconbos @HandstandSam
    Bug Reporting

    View full-size slide

  92. #droidconbos @HandstandSam
    Telescope https://github.com/mattprecious/telescope

    A simple tool to allow easy bug report
    capturing within your app.

    View full-size slide

  93. #droidconbos @HandstandSam
    Memory Leaks

    View full-size slide

  94. #droidconbos @HandstandSam
    Leak Canary https://github.com/square/leakcanary

    A memory leak detection library for Android and Java.

    View full-size slide

  95. #droidconbos @HandstandSam
    Network Inspection

    View full-size slide

  96. #droidconbos @HandstandSam
    Network Debugging

    Stetho

    Chuck

    View full-size slide

  97. #droidconbos @HandstandSam
    DON’T SHIP TO PROD

    View full-size slide

  98. #droidconbos @HandstandSam
    APK ANALYZER

    View full-size slide

  99. #droidconbos @HandstandSam
    APK ANALYZER - DEBUG BUILD

    View full-size slide

  100. #droidconbos @HandstandSam
    APK ANALYZER - RELEASE BUILD

    View full-size slide

  101. #droidconbos @HandstandSam
    CLASSY SHARK
    https://github.com/google/android-classyshark

    View full-size slide

  102. #droidconbos @HandstandSam
    CLASSY SHARK - DEBUG BUILD

    View full-size slide

  103. #droidconbos @HandstandSam
    CLASSY SHARK - RELEASE BUILD

    View full-size slide

  104. #droidconbos @HandstandSam
    CLASSY SHARK - DEBUG BUILD

    View full-size slide

  105. #droidconbos @HandstandSam
    CLASSY SHARK - RELEASE BUILD

    View full-size slide

  106. #droidconbos @HandstandSam
    RECAP
    ● USE GRADLE BUILD VARIANTS
    ● USE EXISTING LIBRARIES IF THEY WORK
    ● START SIMPLE & BE PRAGMATIC

    View full-size slide

  107. #droidconbos @HandstandSam
    IN MY OPINION:
    DEBUG FEATURES THAT ENABLE YOUR TEAM IS JUST AS
    VALUABLE AS WHAT GETS SHIPPED TO PROD.

    View full-size slide

  108. #droidconbos @HandstandSam
    THINK OUTSIDE THE BOX

    View full-size slide

  109. #droidconbos @HandstandSam
    THE END

    View full-size slide