Making Dogfood Builds Testable and Fun

Making Dogfood Builds Testable and Fun

Android apps are fun to dogfood but sometimes complex to configure for testers. To be developed fast, apps need internal builds that don't rely on server teams. To test effectively, internal testers need options to force edge cases and navigate quickly.

To receive feedback, developers need to provide discoverable options to send reports. Tools like Dagger and Gradle build variants have made these wishes a reality for many, and Eric will also dig into how everyone can retrofit existing projects to be quickly configurable.

Learn creative ways to get runtime bindings and debug options up and running in Android apps, old and new.

627ce26ea490d993a49d6b263768ca67?s=128

Eric Cochran

October 27, 2017
Tweet

Transcript

  1. Making Dogfood Builds Testable and Fun Eric Cochran Droidcon London

    October 27, 2017
  2. Why? Build faster

  3. Why? Build faster Force edge cases

  4. Why? Build faster Force edge cases QA feature flags and

    A/B tests
  5. Why? Build faster Force edge cases QA feature flags and

    A/B tests Get actionable feedback from QA
  6. How? Gradle build flavors

  7. How? Gradle build flavors Dependency inversion

  8. How? Gradle build flavors Dependency inversion Amazing libraries

  9. How? Gradle build flavors Dependency inversion Amazing libraries Google Play

    private listing
  10. Gradle buildTypes { debug { applicationIdSuffix 'debug' } } productFlavors

    { internal { applicationIdSuffix 'internal' } }
  11. Gradle

  12. Gradle sourceSets { internal { java.srcDirs = ['internal/java'] // array.

    res.srcDirs = ['internal/res'] // array. manifest.srcFile 'internal/AndroidManifest.xml' // single. } // etc. }
  13. Gradle internalImplementation 'com.example.debugging-library:logging:1.0.0' productionImplementation 'com.example.production-library:crash-reporting:1.0.0'

  14. Gradle Build types for signing Flavors for changes

  15. Add controls for behavior Indirection interface Tweeter { void postTweet();

    } class RealTweeter implements Tweeter { @Override public void postTweet() { // Post! } }
  16. Add controls for behavior Indirection class LoggingTweeter implements Tweeter {

    final Tweeter delegate; final Logger logger; boolean log; @Override public void postTweet() { delegate.postTweet(); if (log) logger.logTweet(); } void log(boolean log) { this.log = log; } }
  17. Add controls for behavior Indirection class DebugView { @Inject LoggingTweeter

    loggingTweeter; { logTweetsCheckbox().setOnCheckedChangeListener((buttonView, isChecked) -> { loggingTweeter.log(isChecked); }); } }
  18. Dependency Inversion Zero-argument factory methods for every type. —Gregory Kick

    Control implementation and behavior from higher in the graph.
  19. Pushing down behavior controls in Android Views created with reflection

    class ServiceContextWrapper extends ContextWrapper { final String serviceName; final Object service; LayoutInflater inflater; @Override @Nullable public Object getSystemService(String name) { if (serviceName.equals(name)) return service; if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (inflater == null) { inflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); } return inflater; } return super.getSystemService(name); } }
  20. Add controls for behavior src/production/java class HomeLayoutFactory implements ViewFactory {

    @Override public View create(Context context, ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(context); return inflater.inflate(R.layout.home, parent, false); } } src/internal/java class HomeLayoutFactory implements ViewFactory { @Override public View create(Context context, ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(context); return inflater.inflate(R.layout.home_debug, parent, false); } }
  21. Add controls for behavior <FrameLayout> <android.support.v4.widget.DrawerLayout> <include layout="@layout/home"/> <com.example.DebugView android:layout_gravity="end"

    android:clipToPadding="false" android:fitsSystemWindows="true"/> </android.support.v4.widget.DrawerLayout> </FrameLayout> src/internal/rest/layout/home_debug.xml
  22. Add controls for behavior <FrameLayout> <android.support.v4.widget.DrawerLayout> <include layout="@layout/home"/> <com.example.DebugView android:layout_gravity="end"

    android:clipToPadding="false" android:fitsSystemWindows="true"/> </android.support.v4.widget.DrawerLayout> </FrameLayout> src/internal/rest/layout/home_debug.xml DebugView.java setOnApplyWindowInsetsListener((v, insets) -> { // Do not consume insets. DebugView.this.onApplyWindowInsets(insets); return insets; });
  23. Practical Usage

  24. A/B tests interface AbTestProvider { enum Transportation { TRAIN, PLANE,

    BOAT } boolean autoScroll(); Transportation defaultTransportation(); } class InternalAbTestProvider implements AbTestProvider { // with getters and setters, modified by debug view. } class ProductionAbTestProvider implements AbTestProvider { @Override public boolean autoScroll() { return firebaseRemoteConfigs.valueOf("auto_scroll"); } @Override public Transportation defaultTransportation() { return parseTransportation(firebaseRemoteConfigs.valueOf("transportation")); } }
  25. Rx-Preferences (SharedPreferences wrapper) interface Preference<T> { interface Converter<T> { @NonNull

    T deserialize(@NonNull String serialized); @NonNull String serialize(@NonNull T value); } @NonNull T get(); void set(@NonNull T value); boolean isSet(); void delete(); } https://github.com/f2prateek/rx-preferences
  26. Retrofit interface NamesService { @GET Call<List<String>> names(@Url String url); }

    // Empty state. @AppScope @Provides static NamesService provideNamesService() { return url -> Calls.response(Collections.emptyList()); } https://github.com/square/retrofit/tree/master/retrofit-mock
  27. OkHttp Interceptors class NoNetworkInterceptor implements Interceptor { volatile boolean noNetwork;

    void setNoNetwork(boolean noNetwork) { this.noNetwork = noNetwork; } @Override public Response intercept(Chain chain) throws IOException { if (noNetwork) throw new IOException("You disabled the network!"); return chain.proceed(chain.request()); } }
  28. OkHttp Logging Interceptor @AppScope @Provides static OkHttpClient provideOkHttpClient(Cache cache) {

    return new OkHttpClient.Builder().cache(cache) .addInterceptor(new GzipRequestInterceptor()) // ... .addInterceptor(new HttpLoggingInterceptor()) .build(); } https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
  29. Telescope https://github.com/mattprecious/telescope <com.mattprecious.telescope.TelescopeLayout> <android.support.v4.widget.DrawerLayout> <include layout="@layout/home"/> <com.example.DebugView/> </android.support.v4.widget.DrawerLayout> </com.mattprecious.telescope.TelescopeLayout> src/internal/res/layout/home_debug.xml

  30. Telescope https://github.com/mattprecious/telescope class HomeLayoutFactory implements ViewFactory { @Override public View

    create(Context context, ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(context); TelescopeLayout layout = (TelescopeLayout) inflater.inflate(R.layout.home_telescope, parent, false); layout.setLens(new EmailDeviceInfoLens(context, "Glitch", "ecochran@pinterest.com")); return layout; } } src/internal/java
  31. Telescope

  32. Leak Canary Use RefWatcher everywhere and give the no-op source

    set to production. internalImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanaryVersion" productionImplementation “com.squareup.leakcanary:leakcanary-android-no-op:$leakcanaryVersion” https://github.com/square/leakcanary
  33. Leak Canary Use RefWatcher everywhere and give the no-op source

    set to production. internalImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanaryVersion" productionImplementation “com.squareup.leakcanary:leakcanary-android-no-op:$leakcanaryVersion” Extend DisplayLeakService and post notifications remotely automatically. https://github.com/square/leakcanary
  34. Publish! Use Google Play! Auto updates! Pricing & Distribution section

    > Restrict Distribution https://support.google.com/a/answer/2494992
  35. Credits Matt Precious Debug Builds: A New Hope https://youtu.be/Ae4vqz29W9U u2020

    https://github.com/JakeWharton/u2020 Android Studio team! Gradle team! Open source project creators!
  36. @Eric_Cochran github.com/NightlyNexus nightlynexus.com