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

Debug Builds: A New Hope (Droidcon MTL 2015)

Debug Builds: A New Hope (Droidcon MTL 2015)

The Cash team at Square moves fast. Really fast. Learn about the tools that we use to eliminate external dependencies and move as fast as possible.

In this talk, I’ll show you how to:
• Quickly verify all of the states of your app by eliminating the server.
• Build and test a server-supported feature before the server is ready.
• Add shortcuts to accelerate your edit-verify cycle.
• Make it easy to submit bug reports with screenshots & full context.

Matthew Precious

April 10, 2015
Tweet

More Decks by Matthew Precious

Other Decks in Programming

Transcript

  1. Debug Builds
    A NEW HOPE
    Matt Precious

    View Slide

  2. Sample code: U+2020
    http://github.com/JakeWharton/u2020

    View Slide

  3. What we’ll need

    View Slide

  4. What we’ll need
    • Gradle build variants

    View Slide

  5. What we’ll need
    • Gradle build variants
    • Dagger

    View Slide

  6. What we’ll need
    • Gradle build variants
    • Dagger
    • Retrofit

    View Slide

  7. What we’ll need
    • Gradle build variants
    • Dagger
    • Retrofit
    • OkHttp

    View Slide

  8. What we’ll need
    • Gradle build variants
    • Dagger
    • Retrofit
    • OkHttp
    • Timber

    View Slide

  9. Build configuration

    View Slide

  10. Build configuration
    • Types
    • debug
    • release

    View Slide

  11. Build configuration
    • Types
    • debug
    • release
    • Flavors
    • internal
    • production

    View Slide

  12. Build configuration
    • Types
    • debug
    • release
    • Flavors
    • internal
    • production
    • Variants

    View Slide

  13. Build configuration
    • Types
    • debug
    • release
    • Flavors
    • internal
    • production
    • Variants
    • internalDebug

    View Slide

  14. Build configuration
    • Types
    • debug
    • release
    • Flavors
    • internal
    • production
    • Variants
    • internalDebug
    • internalRelease

    View Slide

  15. Build configuration
    • Types
    • debug
    • release
    • Flavors
    • internal
    • production
    • Variants
    • internalDebug
    • internalRelease
    • productionDebug

    View Slide

  16. Build configuration
    • Types
    • debug
    • release
    • Flavors
    • internal
    • production
    • Variants
    • internalDebug
    • internalRelease
    • productionDebug
    • productionRelease

    View Slide

  17. Debug drawer

    View Slide

  18. Debug drawer

    View Slide

  19. MainActivity (src/main)
    public final class MainActivity extends Activity {


    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    }

    }a

    View Slide

  20. Activity
    Content
    Container
    Content

    View Slide

  21. Activity
    Content
    Container
    Content
    Debug
    Drawer
    Debug View

    View Slide

  22. AppContainer (src/main)
    public interface AppContainer {

    ViewGroup bind(Activity activity);

    }a

    View Slide

  23. AppContainer (src/main)
    public interface AppContainer {

    ViewGroup bind(Activity activity);


    AppContainer DEFAULT = new AppContainer() {

    @Override public ViewGroup bind(Activity activity) {

    return findById(activity, android.R.id.content);

    }

    };

    }a

    View Slide

  24. UiModule (src/main)
    @Provides @Singleton
    AppContainer provideAppContainer() {

    return AppContainer.DEFAULT;

    }a

    View Slide

  25. MainActivity (src/main)
    public final class MainActivity extends Activity {


    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    }a

    }a

    View Slide

  26. MainActivity (src/main)
    public final class MainActivity extends Activity {

    @Inject AppContainer appContainer;


    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    ObjectGraph appGraph = Injector.obtain(getApplication());

    appGraph.inject(this);
    !
    setContentView(R.layout.main_activity);

    }a

    }a

    View Slide

  27. MainActivity (src/main)
    public final class MainActivity extends Activity {

    @Inject AppContainer appContainer;


    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    ObjectGraph appGraph = Injector.obtain(getApplication());

    appGraph.inject(this);


    ViewGroup container = appContainer.bind(this);
    !
    setContentView(R.layout.main_activity);

    }a

    }a

    View Slide

  28. MainActivity (src/main)
    public final class MainActivity extends Activity {

    @Inject AppContainer appContainer;


    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    ObjectGraph appGraph = Injector.obtain(getApplication());

    appGraph.inject(this);


    ViewGroup container = appContainer.bind(this);
    !
    setContentView(R.layout.main_activity);

    }a

    }a

    View Slide

  29. MainActivity (src/main)
    public final class MainActivity extends Activity {

    @Inject AppContainer appContainer;


    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    ObjectGraph appGraph = Injector.obtain(getApplication());

    appGraph.inject(this);


    ViewGroup container = appContainer.bind(this);


    getLayoutInflater().inflate(R.layout.main_activity, container);

    }a

    }a

    View Slide

  30. debug_activity_frame (src/internalDebug)




    android:id="@+id/debug_drawer"

    android:layout_gravity="right"
    android:background="#ee212121"
    />


    View Slide

  31. debug_activity_frame (src/internalDebug)




    android:id="@+id/debug_drawer"

    android:layout_gravity="right"
    android:background="#ee212121"

    />


    View Slide

  32. debug_activity_frame (src/internalDebug)




    android:id="@+id/debug_drawer"

    android:layout_gravity="right"
    android:background="#ee212121"

    />


    View Slide

  33. DebugView (src/internalDebug)
    public final class DebugView extends FrameLayout {
    !
    public DebugView(Context context) {

    this(context, null);

    }a


    public DebugView(Context context, AttributeSet attrs) {

    super(context, attrs);

    Injector.obtain(context).inject(this);


    LayoutInflater.from(context).inflate(R.layout.debug_view_content, this);
    }a
    }a

    View Slide

  34. DebugView (src/internalDebug)
    public final class DebugView extends FrameLayout {
    !
    public DebugView(Context context) {

    this(context, null);

    }a


    public DebugView(Context context, AttributeSet attrs) {

    super(context, attrs);

    Injector.obtain(context).inject(this);


    LayoutInflater.from(context).inflate(R.layout.debug_view_content, this);
    }a
    }a

    View Slide

  35. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    }a
    }a

    View Slide

  36. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    activity.setContentView(R.layout.debug_activity_frame);
    }a
    }a

    View Slide

  37. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    activity.setContentView(R.layout.debug_activity_frame);
    !
    ViewGroup drawer = findById(activity, R.id.debug_drawer);

    DebugView debugView = new DebugView(activity);

    drawer.addView(debugView);
    }a
    }a

    View Slide

  38. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    activity.setContentView(R.layout.debug_activity_frame);
    !
    ViewGroup drawer = findById(activity, R.id.debug_drawer);

    DebugView debugView = new DebugView(activity);

    drawer.addView(debugView);
    !
    return findById(activity, R.id.debug_content);
    !
    }a
    }a

    View Slide

  39. DebugUiModule (src/internalDebug)
    @Provides @Singleton
    AppContainer provideAppContainer(DebugAppContainer debugAppContainer) {

    return debugAppContainer;

    }a

    View Slide

  40. Endpoints

    View Slide

  41. Endpoints

    View Slide

  42. ApiModule (src/main)
    public static final String PRODUCTION_API_URL = "https://api.github.com";


    @Provides @Singleton Endpoint provideEndpoint() {

    return Endpoints.newFixedEndpoint(PRODUCTION_API_URL);

    }

    View Slide

  43. ApiEndpoints (src/internalDebug)
    public enum ApiEndpoints {

    PRODUCTION,

    STAGING,

    CUSTOM,

    }aa

    View Slide

  44. ApiEndpoints (src/internalDebug)
    public enum ApiEndpoints {

    PRODUCTION("Production", ApiModule.PRODUCTION_API_URL),

    STAGING("Staging", "https://api.staging.github.com/"),

    CUSTOM("Custom", null);


    public final String name;

    public final String url;


    ApiEndpoints(String name, String url) {

    this.name = name;

    this.url = url;

    }a

    }aa

    View Slide

  45. DebugDataModule (src/internalDebug)
    @Provides @Singleton @ApiEndpoint

    StringPreference provideEndpointPreference(SharedPreferences preferences) {

    return new StringPreference(preferences, “debug_endpoint",
    ApiEndpoints.STAGING.url);

    }

    View Slide

  46. DebugApiModule (src/internalDebug)
    @Provides @Singleton

    Endpoint provideEndpoint(@ApiEndpoint StringPreference apiEndpoint) {

    return Endpoints.newFixedEndpoint(apiEndpoint.get());

    }

    View Slide

  47. debug_view_content (src/internalDebug)
    style="@style/Widget.U2020.DebugDrawer.RowTitle"
    android:text="Endpoint"

    />
    !

    View Slide

  48. DebugView (src/internalDebug)
    private void setupNetworkSection() {

    }a

    View Slide

  49. DebugView (src/internalDebug)
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    final ApiEndpoints currentEndpoint = ApiEndpoints.from(networkEndpoint.get());

    }aa

    View Slide

  50. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_endpoint) Spinner endpointView;
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    final ApiEndpoints currentEndpoint = ApiEndpoints.from(networkEndpoint.get());

    final EnumAdapter endpointAdapter =

    new EnumAdapter<>(getContext(), ApiEndpoints.class);

    endpointView.setAdapter(endpointAdapter);

    }aa

    View Slide

  51. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_endpoint) Spinner endpointView;
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    final ApiEndpoints currentEndpoint = ApiEndpoints.from(networkEndpoint.get());

    final EnumAdapter endpointAdapter =

    new EnumAdapter<>(getContext(), ApiEndpoints.class);

    endpointView.setAdapter(endpointAdapter);

    }aa

    View Slide

  52. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_endpoint) Spinner endpointView;
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    final ApiEndpoints currentEndpoint = ApiEndpoints.from(networkEndpoint.get());

    final EnumAdapter endpointAdapter =

    new EnumAdapter<>(getContext(), ApiEndpoints.class);

    endpointView.setAdapter(endpointAdapter);

    endpointView.setSelection(currentEndpoint.ordinal());

    }aa

    View Slide

  53. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_endpoint) Spinner endpointView;
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    // ...

    endpointView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    ApiEndpoints selected = endpointAdapter.getItem(position);

    if (selected != currentEndpoint) {

    if (selected == ApiEndpoints.CUSTOM) {

    showCustomEndpointDialog();

    } else {

    setEndpointAndRelaunch(selected.url);

    }a

    }a

    });

    }aa

    View Slide

  54. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_endpoint) Spinner endpointView;
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    // ...

    endpointView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    ApiEndpoints selected = endpointAdapter.getItem(position);

    if (selected != currentEndpoint) {

    if (selected == ApiEndpoints.CUSTOM) {

    showCustomEndpointDialog();

    } else {

    setEndpointAndRelaunch(selected.url);

    }a

    }a

    });

    }aa

    View Slide

  55. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_endpoint) Spinner endpointView;
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    // ...

    endpointView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    ApiEndpoints selected = endpointAdapter.getItem(position);

    if (selected != currentEndpoint) {

    if (selected == ApiEndpoints.CUSTOM) {

    showCustomEndpointDialog();

    } else {

    setEndpointAndRelaunch(selected.url);

    }a

    }a

    });

    }aa

    View Slide

  56. DebugView (src/internalDebug)
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    // ...

    }aa
    !
    private void setEndpointAndRelaunch(String endpoint) {

    networkEndpoint.set(endpoint);


    ProcessPhoenix.triggerRebirth(getContext());

    }a

    View Slide

  57. DebugView (src/internalDebug)
    @Inject @ApiEndpoint StringPreference networkEndpoint;
    !
    private void setupNetworkSection() {

    // ...

    }aa
    !
    private void setEndpointAndRelaunch(String endpoint) {

    networkEndpoint.set(endpoint);


    ProcessPhoenix.triggerRebirth(getContext());

    }a
    !
    !
    !
    https://gist.github.com/JakeWharton

    View Slide

  58. Charles

    View Slide

  59. Charles
    • HTTP proxy

    View Slide

  60. Charles
    • HTTP proxy
    • Monitor network traffic

    View Slide

  61. Charles
    • HTTP proxy
    • Monitor network traffic
    • Request

    View Slide

  62. Charles
    • HTTP proxy
    • Monitor network traffic
    • Request
    • Response

    View Slide

  63. Charles
    • HTTP proxy
    • Monitor network traffic
    • Request
    • Response
    • Headers

    View Slide

  64. Charles
    • HTTP proxy
    • Monitor network traffic
    • Request
    • Response
    • Headers
    • Intercept and modify requests and responses

    View Slide

  65. Charles
    • HTTP proxy
    • Monitor network traffic
    • Request
    • Response
    • Headers
    • Intercept and modify requests and responses
    • http://www.charlesproxy.com/

    View Slide

  66. Network Proxy

    View Slide

  67. Network Proxy

    View Slide

  68. Network Proxy

    View Slide

  69. DataModule (src/main)
    @Provides @Singleton OkHttpClient provideOkHttpClient(Application app) {

    return createOkHttpClient(app);

    }
    !
    static OkHttpClient createOkHttpClient(Application app) {

    OkHttpClient client = new OkHttpClient();

    // ...

    return client;

    }

    View Slide

  70. NetworkProxyPreference (src/internalDebug)
    public final class NetworkProxyPreference extends StringPreference {

    public NetworkProxyPreference(SharedPreferences preferences, String key) {

    super(preferences, key);

    }


    public @Nullable Proxy getProxy() {

    if (!isSet()) return null;


    String[] parts = get().split(":", 2);

    String host = parts[0];

    int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 80;


    return new Proxy(HTTP, InetSocketAddress.createUnresolved(host, port));

    }

    }

    View Slide

  71. DebugDataModule (src/internalDebug)
    @Provides @Singleton
    OkHttpClient provideOkHttpClient(Application app) {

    OkHttpClient client = DataModule.createOkHttpClient(app);

    return client;

    }a

    View Slide

  72. DebugDataModule (src/internalDebug)
    @Provides @Singleton
    OkHttpClient provideOkHttpClient(Application app,

    NetworkProxyPreference networkProxy) {

    OkHttpClient client = DataModule.createOkHttpClient(app);

    client.setProxy(networkProxy.getProxy());

    return client;

    }a

    View Slide

  73. DebugDataModule (src/internalDebug)
    @Provides @Singleton
    OkHttpClient provideOkHttpClient(Application app,

    NetworkProxyPreference networkProxy) {

    OkHttpClient client = DataModule.createOkHttpClient(app);

    client.setProxy(networkProxy.getProxy());
    client.setSslSocketFactory(createBadSslSocketFactory());

    return client;

    }a

    View Slide

  74. debug_view_content (src/internalDebug)
    style="@style/Widget.U2020.DebugDrawer.RowTitle"

    android:text="Proxy"

    />


    View Slide

  75. DebugView (src/internalDebug)
    private void setupNetworkSection() {
    // ...
    }aa

    View Slide

  76. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_proxy) Spinner networkProxyView;
    @Inject NetworkProxyPreference networkProxy;
    !
    private void setupNetworkSection() {
    // ...

    final ProxyAdapter proxyAdapter = new ProxyAdapter(getContext(), networkProxy);

    networkProxyView.setAdapter(proxyAdapter);

    }aa

    View Slide

  77. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_proxy) Spinner networkProxyView;
    @Inject NetworkProxyPreference networkProxy;
    !
    private void setupNetworkSection() {
    // ...

    final ProxyAdapter proxyAdapter = new ProxyAdapter(getContext(), networkProxy);

    networkProxyView.setAdapter(proxyAdapter);
    int proxyPosition = networkProxy.isSet() ? ProxyAdapter.PROXY : ProxyAdapter.NONE;

    networkProxyView.setSelection(proxyPosition);

    }aa

    View Slide

  78. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_proxy) Spinner networkProxyView;
    @Inject NetworkProxyPreference networkProxy;
    @Inject OkHttpClient client;
    !
    private void setupNetworkSection() {
    // ...

    networkProxyView.setOnItemSelectedListener((adapterView, view, position, d) -> {

    if (position == ProxyAdapter.NONE) {

    networkProxy.delete();

    client.setProxy(null);

    } else {

    showNewNetworkProxyDialog();

    }a

    });
    }aa

    View Slide

  79. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_proxy) Spinner networkProxyView;
    @Inject NetworkProxyPreference networkProxy;
    @Inject OkHttpClient client;
    !
    private void setupNetworkSection() {
    // ...
    !
    networkProxyView.setOnItemSelectedListener((adapterView, view, position, d) -> {

    if (position == ProxyAdapter.NONE) {

    networkProxy.delete();

    client.setProxy(null);

    } else {

    showNewNetworkProxyDialog();

    }a

    });
    }aa

    View Slide

  80. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_proxy) Spinner networkProxyView;
    @Inject NetworkProxyPreference networkProxy;
    @Inject OkHttpClient client;
    !
    private void setupNetworkSection() {
    // ...

    networkProxyView.setOnItemSelectedListener((adapterView, view, position, d) -> {

    if (position == ProxyAdapter.NONE) {

    networkProxy.delete();

    client.setProxy(null);

    } else {

    showNewNetworkProxyDialog();

    }a

    });
    }aa

    View Slide

  81. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_proxy) Spinner networkProxyView;
    @Inject NetworkProxyPreference networkProxy;
    @Inject OkHttpClient client;
    !
    private void setupNetworkSection() {
    // ...
    }aa
    !
    private void setProxyHost(String host) {

    networkProxy.set(host);


    Proxy proxy = networkProxy.getProxy();

    client.setProxy(proxy);

    }

    View Slide

  82. Mock Mode

    View Slide

  83. GithubService (src/main)
    public interface GithubService {

    @GET("/search/repositories") //

    Observable repositories(

    @Query("q") SearchQuery query,

    @Query("sort") Sort sort,

    @Query("order") Order order);

    }

    View Slide

  84. ApiModule (src/main)
    @Provides @Singleton
    GithubService provideGithubService(RestAdapter restAdapter) {

    return restAdapter.create(GithubService.class);

    }

    View Slide

  85. ApiEndpoints (src/internalDebug)
    public enum ApiEndpoints {

    PRODUCTION("Production", ApiModule.PRODUCTION_API_URL),

    STAGING("Staging", "https://api.staging.github.com/"),

    CUSTOM("Custom", null);


    public final String name;

    public final String url;


    ApiEndpoints(String name, String url) {

    this.name = name;

    this.url = url;

    }a

    }aa

    View Slide

  86. ApiEndpoints (src/internalDebug)
    public enum ApiEndpoints {

    PRODUCTION("Production", ApiModule.PRODUCTION_API_URL),

    STAGING("Staging", "https://api.staging.github.com/"),
    MOCK_MODE("Mock mode", "mock://"),

    CUSTOM("Custom", null);


    public final String name;

    public final String url;


    ApiEndpoints(String name, String url) {

    this.name = name;

    this.url = url;

    }a

    }aa

    View Slide

  87. ApiEndpoints (src/internalDebug)
    public enum ApiEndpoints {

    // ...

    public static boolean isMockMode(String endpoint) {

    return from(endpoint) == MOCK_MODE;

    }a
    }aa

    View Slide

  88. DebugDataModule (src/internalDebug)
    @Provides @Singleton @IsMockMode
    boolean provideIsMockMode(@ApiEndpoint StringPreference endpoint) {

    return ApiEndpoints.isMockMode(endpoint.get());

    }

    View Slide

  89. MockRepositories (src/internalDebug)
    static final Repository DAGGER = new Repository.Builder()

    .name("dagger")

    .owner(SQUARE)

    .description("A fast dependency injector for Android and Java.")

    .forks(574)

    .stars(3085)

    .htmlUrl("https://github.com/square/dagger")

    .updatedAt(DateTime.parse("2015-03-05"))

    .build();
    !
    // ...

    View Slide

  90. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {


    @Inject MockGithubService() { }

    }a

    View Slide

  91. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {


    @Inject MockGithubService() { }


    @Override public Observable repositories(SearchQuery query,
    Sort sort, Order order) {
    return Observable.just(new RepositoriesResponse(Arrays.asList(

    BUTTERKNIFE,

    DAGGER,

    OKHTTP,

    OKIO,

    PICASSO,

    RETROFIT,

    TELESCOPE,

    U2020)));

    }

    }a

    View Slide

  92. DebugApiModule (src/internalDebug)
    @Provides @Singleton

    GithubService provideGithubService(RestAdapter restAdapter,
    MockRestAdapter mockRestAdapter, @IsMockMode boolean isMockMode,
    MockGithubService mockService) {

    if (isMockMode) {

    return mockRestAdapter.create(GithubService.class, mockService);

    }

    return restAdapter.create(GithubService.class);

    }

    View Slide

  93. View Slide

  94. :)

    View Slide

  95. :) Loading Error
    Empty One result Lots of results

    View Slide

  96. :) Loading Error
    Empty One result Lots of results

    View Slide

  97. :) Loading Error
    Empty One result Lots of results

    View Slide

  98. debug_view_content (src/internalDebug)
    style="@style/Widget.U2020.DebugDrawer.RowTitle"

    android:text="Delay"

    />



    style="@style/Widget.U2020.DebugDrawer.RowTitle"

    android:text="Error"

    />


    View Slide

  99. DebugApiModule
    @Provides @Singleton
    MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter,

    SharedPreferences preferences) {

    MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);

    AndroidMockValuePersistence.install(mockRestAdapter, preferences);

    return mockRestAdapter;

    }

    View Slide

  100. DebugView (src/internalDebug)
    private void setupNetworkSection() {
    // ...
    }aa

    View Slide

  101. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_delay) Spinner networkDelayView;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());

    networkDelayView.setAdapter(delayAdapter);

    }aa

    View Slide

  102. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_delay) Spinner networkDelayView;
    @Inject MockRestAdapter mockRestAdapter;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());

    networkDelayView.setAdapter(delayAdapter);

    networkDelayView.setSelection(

    NetworkDelayAdapter.getPositionForValue(mockRestAdapter.getDelay()));
    }aa

    View Slide

  103. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_delay) Spinner networkDelayView;
    @Inject MockRestAdapter mockRestAdapter;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());

    networkDelayView.setAdapter(delayAdapter);

    networkDelayView.setSelection(

    NetworkDelayAdapter.getPositionForValue(mockRestAdapter.getDelay()));
    }aa

    View Slide

  104. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_delay) Spinner networkDelayView;
    @Inject MockRestAdapter mockRestAdapter;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());

    networkDelayView.setAdapter(delayAdapter);

    networkDelayView.setSelection(

    NetworkDelayAdapter.getPositionForValue(mockRestAdapter.getDelay()));

    networkDelayView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    int selected = delayAdapter.getItem(position);

    if (selected != mockRestAdapter.getDelay()) {

    mockRestAdapter.setDelay(selected);

    }a

    });
    }aa

    View Slide

  105. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_delay) Spinner networkDelayView;
    @Inject MockRestAdapter mockRestAdapter;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());

    networkDelayView.setAdapter(delayAdapter);

    networkDelayView.setSelection(

    NetworkDelayAdapter.getPositionForValue(mockRestAdapter.getDelay()));

    networkDelayView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    int selected = delayAdapter.getItem(position);

    if (selected != mockRestAdapter.getDelay()) {

    mockRestAdapter.setDelay(selected);

    }a

    });
    }aa

    View Slide

  106. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_error) Spinner networkErrorView;
    @Inject MockRestAdapter mockRestAdapter;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkErrorAdapter errorAdapter = new NetworkErrorAdapter(getContext());

    networkErrorView.setAdapter(errorAdapter);

    networkErrorView.setSelection(

    NetworkErrorAdapter.getPositionForValue(mockRestAdapter.getErrorPercentage()));

    networkErrorView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    int selected = errorAdapter.getItem(position);

    if (selected != mockRestAdapter.getErrorPercentage()) {

    mockRestAdapter.setErrorPercentage(selected);

    }a

    });
    }aa

    View Slide

  107. DebugView (src/internalDebug)
    @InjectView(R.id.debug_network_error) Spinner networkErrorView;
    @Inject MockRestAdapter mockRestAdapter;
    !
    private void setupNetworkSection() {
    // ...
    !
    final NetworkErrorAdapter errorAdapter = new NetworkErrorAdapter(getContext());

    networkErrorView.setAdapter(errorAdapter);

    networkErrorView.setSelection(

    NetworkErrorAdapter.getPositionForValue(mockRestAdapter.getErrorPercentage()));

    networkErrorView.setOnItemSelectedListener((adapterView, view, position, id) -> {

    int selected = errorAdapter.getItem(position);

    if (selected != mockRestAdapter.getErrorPercentage()) {

    mockRestAdapter.setErrorPercentage(selected);

    }a

    });
    }aa

    View Slide

  108. :) Loading Error
    Empty One result Lots of results

    View Slide

  109. :) Loading Error
    Empty One result Lots of results
    ✔ ✔ ✔

    View Slide

  110. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {


    @Inject MockGithubService() {

    }a


    @Override public Observable repositories(SearchQuery query,
    Sort sort, Order order) {
    return Observable.just(new RepositoriesResponse(Arrays.asList(

    BUTTERKNIFE,

    DAGGER,

    OKHTTP,

    OKIO,

    PICASSO,

    RETROFIT,

    TELESCOPE,

    U2020)));

    }a

    }aa

    View Slide

  111. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {


    @Inject MockGithubService() {

    }a


    @Override public Observable repositories(SearchQuery query,
    Sort sort, Order order) {
    return Observable.just(new RepositoriesResponse(Arrays.asList(

    BUTTERKNIFE,

    DAGGER,

    OKHTTP,

    OKIO,

    PICASSO,

    RETROFIT,

    TELESCOPE,

    U2020)));

    }a

    }aa

    View Slide

  112. new RepositoriesResponse(Arrays.asList(

    BUTTERKNIFE,

    DAGGER,

    OKHTTP,

    OKIO,

    PICASSO,

    RETROFIT,

    TELESCOPE,

    U2020))

    View Slide

  113. MockRepositoriesResponse (src/internalDebug)
    public enum MockRepositoriesResponse {

    SUCCESS("Success", new RepositoriesResponse(Arrays.asList(

    BUTTERKNIFE,

    DAGGER,

    OKHTTP,

    OKIO,

    PICASSO,

    RETROFIT,

    TELESCOPE,

    U2020)));


    public final String name;

    public final RepositoriesResponse response;


    MockRepositoriesResponse(String name, RepositoriesResponse response) {

    this.name = name;

    this.response = response;

    }a

    }aa

    View Slide

  114. MockRepositoriesResponse (src/internalDebug)
    public enum MockRepositoriesResponse {

    SUCCESS("Success", new RepositoriesResponse(Arrays.asList(

    BUTTERKNIFE,

    DAGGER,

    OKHTTP,

    OKIO,

    PICASSO,

    RETROFIT,

    TELESCOPE,

    U2020))),
    ONE("One", new RepositoriesResponse(Collections.singletonList(DAGGER))),

    EMPTY("Empty", new RepositoriesResponse(null));


    public final String name;

    public final RepositoriesResponse response;


    MockRepositoriesResponse(String name, RepositoriesResponse response) {

    this.name = name;

    this.response = response;

    }a

    }aa

    View Slide

  115. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {

    // ...

    }aa

    View Slide

  116. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {

    // ...


    public > T getResponse(Class responseClass) { // }

    public > void setResponse(Class responseClass, T value) { // }

    }aa

    View Slide

  117. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {

    // ...


    public > T getResponse(Class responseClass) { // }

    public > void setResponse(Class responseClass, T value) { // }


    @Override public Observable repositories(SearchQuery query,

    Sort sort, Order order) {

    }a

    }aa

    View Slide

  118. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {

    // ...


    public > T getResponse(Class responseClass) { // }

    public > void setResponse(Class responseClass, T value) { // }


    @Override public Observable repositories(SearchQuery query,

    Sort sort, Order order) {

    RepositoriesResponse response =
    getResponse(MockRepositoriesResponse.class).response;

    }a

    }aa

    View Slide

  119. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {

    // ...


    public > T getResponse(Class responseClass) { // }

    public > void setResponse(Class responseClass, T value) { // }


    @Override public Observable repositories(SearchQuery query,

    Sort sort, Order order) {

    RepositoriesResponse response =
    getResponse(MockRepositoriesResponse.class).response;


    // TODO: Sort the repositories based on the request.

    }a

    }aa

    View Slide

  120. MockGithubService (src/internalDebug)
    @Singleton

    public final class MockGithubService implements GithubService {

    // ...


    public > T getResponse(Class responseClass) { // }

    public > void setResponse(Class responseClass, T value) { // }


    @Override public Observable repositories(SearchQuery query,

    Sort sort, Order order) {

    RepositoriesResponse response =
    getResponse(MockRepositoriesResponse.class).response;

    // TODO: Sort the repositories based on the request.


    return Observable.just(response);

    }a

    }aa

    View Slide

  121. debug_view_content (src/internalDebug)
    style="@style/Widget.U2020.DebugDrawer.RowTitle"

    android:text="Repositories"

    />


    View Slide

  122. DebugView (src/internalDebug)
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    }aa

    View Slide

  123. DebugView (src/internalDebug)
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    }aa

    View Slide

  124. DebugView (src/internalDebug)
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    }aa

    View Slide

  125. DebugView (src/internalDebug)
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    final EnumAdapter adapter = new EnumAdapter<>(getContext(), responseClass);
    spinner.setAdapter(adapter);
    }aa

    View Slide

  126. DebugView (src/internalDebug)
    @Inject @IsMockMode boolean isMockMode;
    !
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    final EnumAdapter adapter = new EnumAdapter<>(getContext(), responseClass);
    spinner.setAdapter(adapter);

    spinner.setEnabled(isMockMode);
    }aa

    View Slide

  127. DebugView (src/internalDebug)
    @Inject @IsMockMode boolean isMockMode;
    @Inject MockGithubService mockGithubService;
    !
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    final EnumAdapter adapter = new EnumAdapter<>(getContext(), responseClass);
    spinner.setAdapter(adapter);

    spinner.setEnabled(isMockMode);

    spinner.setSelection(mockGithubService.getResponse(responseClass).ordinal());

    }aa

    View Slide

  128. DebugView (src/internalDebug)
    @Inject @IsMockMode boolean isMockMode;
    @Inject MockGithubService mockGithubService;
    !
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    final EnumAdapter adapter = new EnumAdapter<>(getContext(), responseClass);
    spinner.setAdapter(adapter);

    spinner.setEnabled(isMockMode);

    spinner.setSelection(mockGithubService.getResponse(responseClass).ordinal());

    spinner.setOnItemSelectedListener((parent, view, position, id) -> {

    T selected = adapter.getItem(position);

    if (selected != mockGithubService.getResponse(responseClass)) {

    mockGithubService.setResponse(responseClass, selected);

    }a

    });

    }aa

    View Slide

  129. DebugView (src/internalDebug)
    @InjectView(R.id.debug_repositories_response) Spinner repositoriesResponseView;
    !
    private void setupMockBehaviorSection() {

    configureResponseSpinner(repositoriesResponseView,
    MockRepositoriesResponse.class);

    }
    !
    private > void configureResponseSpinner(Spinner spinner,

    final Class responseClass) {

    // ...

    }aa

    View Slide

  130. Intent Capturing

    View Slide

  131. Intent Capturing

    View Slide

  132. IntentFactory (src/main)
    public interface IntentFactory {

    }a

    View Slide

  133. IntentFactory (src/main)
    public interface IntentFactory {

    Intent createUrlIntent(String url);

    }a

    View Slide

  134. IntentFactory (src/main)
    public interface IntentFactory {

    Intent createUrlIntent(String url);


    IntentFactory REAL = new IntentFactory() {

    @Override public Intent createUrlIntent(String url) {

    Intent intent = new Intent(Intent.ACTION_VIEW);

    intent.setData(Uri.parse(url));


    return intent;

    }

    };

    }a

    View Slide

  135. DataModule (src/main)
    @Provides @Singleton IntentFactory provideIntentFactory() {

    return IntentFactory.REAL;

    }

    View Slide

  136. ExternalIntentActivity (src/internalDebug)
    public final class ExternalIntentActivity extends Activity {

    }a

    View Slide

  137. ExternalIntentActivity (src/internalDebug)
    public final class ExternalIntentActivity extends Activity {

    public static final String ACTION = "com.jakewharton.u2020.intent.EXTERNAL_INTENT";

    public static final String EXTRA_BASE_INTENT = "debug_base_intent";

    }a

    View Slide

  138. ExternalIntentActivity (src/internalDebug)
    public final class ExternalIntentActivity extends Activity {

    public static final String ACTION = "com.jakewharton.u2020.intent.EXTERNAL_INTENT";

    public static final String EXTRA_BASE_INTENT = "debug_base_intent";
    !
    public static Intent createIntent(Intent baseIntent) {

    Intent intent = new Intent();

    intent.setAction(ACTION);

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    intent.putExtra(EXTRA_BASE_INTENT, baseIntent);

    return intent;

    }b

    }a

    View Slide

  139. ExternalIntentActivity (src/internalDebug)
    public final class ExternalIntentActivity extends Activity {

    public static final String ACTION = "com.jakewharton.u2020.intent.EXTERNAL_INTENT";

    public static final String EXTRA_BASE_INTENT = "debug_base_intent";
    !
    public static Intent createIntent(Intent baseIntent) {

    Intent intent = new Intent();

    intent.setAction(ACTION);

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    intent.putExtra(EXTRA_BASE_INTENT, baseIntent);

    return intent;

    }b

    }a

    View Slide

  140. ExternalIntentActivity (src/internalDebug)
    public final class ExternalIntentActivity extends Activity {

    public static final String ACTION = "com.jakewharton.u2020.intent.EXTERNAL_INTENT";

    public static final String EXTRA_BASE_INTENT = "debug_base_intent";

    // ...

    @Override protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.debug_external_intent_activity);


    Intent baseIntent = getIntent().getParcelableExtra(EXTRA_BASE_INTENT);

    // TODO: Show intent data.

    }

    }a

    View Slide

  141. DebugDataModule (src/internalDebug)
    @Provides @Singleton @CaptureIntents

    BooleanPreference provideCaptureIntentsPreference(SharedPreferences preferences) {

    return new BooleanPreference(preferences, "debug_capture_intents",
    DEFAULT_CAPTURE_INTENTS);

    }

    View Slide

  142. DebugIntentFactory (src/internalDebug)
    @Singleton

    public final class DebugIntentFactory implements IntentFactory {


    @Inject public DebugIntentFactory() { }

    }aa

    View Slide

  143. DebugIntentFactory (src/internalDebug)
    @Singleton

    public final class DebugIntentFactory implements IntentFactory {

    private final IntentFactory realIntentFactory;

    private final boolean isMockMode;

    private final BooleanPreference captureIntents;


    @Inject public DebugIntentFactory( // ... ) { // ... }

    }aa

    View Slide

  144. DebugIntentFactory (src/internalDebug)
    @Singleton

    public final class DebugIntentFactory implements IntentFactory {

    private final IntentFactory realIntentFactory;

    private final boolean isMockMode;

    private final BooleanPreference captureIntents;


    @Inject public DebugIntentFactory( // ... ) { // ... }
    !
    @Override public Intent createUrlIntent(String url) {

    }a

    }aa

    View Slide

  145. DebugIntentFactory (src/internalDebug)
    @Singleton

    public final class DebugIntentFactory implements IntentFactory {

    private final IntentFactory realIntentFactory;

    private final boolean isMockMode;

    private final BooleanPreference captureIntents;


    @Inject public DebugIntentFactory( // ... ) { // ... }
    !
    @Override public Intent createUrlIntent(String url) {

    Intent baseIntent = realIntentFactory.createUrlIntent(url);

    }a

    }aa

    View Slide

  146. DebugIntentFactory (src/internalDebug)
    @Singleton

    public final class DebugIntentFactory implements IntentFactory {

    private final IntentFactory realIntentFactory;

    private final boolean isMockMode;

    private final BooleanPreference captureIntents;


    @Inject public DebugIntentFactory( // ... ) { // ... }
    !
    @Override public Intent createUrlIntent(String url) {

    Intent baseIntent = realIntentFactory.createUrlIntent(url);

    if (isMockMode && captureIntents.get()) {
    return ExternalIntentActivity.createIntent(baseIntent);

    } else {

    return baseIntent;

    }

    }a

    }aa

    View Slide

  147. DebugDataModule (src/internalDebug)
    @Provides @Singleton
    IntentFactory provideIntentFactory(DebugIntentFactory debugIntentFactory) {

    return debugIntentFactory;

    }

    View Slide

  148. Bug Reporting

    View Slide

  149. Bug Reporting

    View Slide

  150. Bug Reporting

    View Slide

  151. Telescope

    View Slide

  152. Telescope
    • Press and hold 2 fingers to trigger a bug report

    View Slide

  153. Telescope
    • Press and hold 2 fingers to trigger a bug report
    • Takes a screenshot

    View Slide

  154. Telescope
    • Press and hold 2 fingers to trigger a bug report
    • Takes a screenshot
    • Calls your listener to handle the report

    View Slide

  155. Telescope
    • Press and hold 2 fingers to trigger a bug report
    • Takes a screenshot
    • Calls your listener to handle the report
    • Send an email

    View Slide

  156. Telescope
    • Press and hold 2 fingers to trigger a bug report
    • Takes a screenshot
    • Calls your listener to handle the report
    • Send an email
    • Open a ticket via an API

    View Slide

  157. Telescope
    • Press and hold 2 fingers to trigger a bug report
    • Takes a screenshot
    • Calls your listener to handle the report
    • Send an email
    • Open a ticket via an API
    • https://github.com/mattprecious/telescope

    View Slide

  158. LumberYard (src/internal)
    @Singleton
    public final class LumberYard {
    !
    @Inject public LumberYard() { }
    }a

    View Slide

  159. LumberYard (src/internal)
    @Singleton
    public final class LumberYard {
    private final Deque entries = new ArrayDeque<>(BUFFER_SIZE + 1);
    !
    @Inject public LumberYard() { }
    !
    private synchronized void addEntry(Entry entry) { // ... }
    }a

    View Slide

  160. LumberYard (src/internal)
    @Singleton
    public final class LumberYard {
    private final Deque entries = new ArrayDeque<>(BUFFER_SIZE + 1);
    !
    @Inject public LumberYard() { }
    !
    public Timber.Tree tree() {

    return (DebugTree) logMessage(priority, tag, message) -> {

    addEntry(new Entry(priority, tag, message));

    };

    }b
    !
    private synchronized void addEntry(Entry entry) { // ... }
    }a

    View Slide

  161. LumberYard (src/internal)
    @Singleton
    public final class LumberYard {
    private final Deque entries = new ArrayDeque<>(BUFFER_SIZE + 1);
    !
    @Inject public LumberYard() { }
    !
    public Timber.Tree tree() {
    // ...
    }b
    !
    private synchronized void addEntry(Entry entry) { // ... }
    !
    public Observable save() { // ... }
    }a

    View Slide

  162. BugReportLens (src/internal)
    public final class BugReportLens implements Lens {
    private final Context context;

    private final LumberYard lumberYard;


    private File screenshot;


    public BugReportLens(Context context, LumberYard lumberYard) { // ... }
    }aa

    View Slide

  163. BugReportLens (src/internal)
    public final class BugReportLens implements Lens {
    private final Context context;

    private final LumberYard lumberYard;


    private File screenshot;


    public BugReportLens(Context context, LumberYard lumberYard) { // ... }
    !
    @Override public void onCapture(File screenshot) {

    this.screenshot = screenshot;

    // TODO: Show bug report dialog.

    }a
    }aa

    View Slide

  164. BugReportLens (src/internal)
    public final class BugReportLens implements Lens {
    private final Context context;

    private final LumberYard lumberYard;


    private File screenshot;


    public BugReportLens(Context context, LumberYard lumberYard) { // ... }
    !
    @Override public void onCapture(File screenshot) {

    this.screenshot = screenshot;

    // TODO: Show bug report dialog.

    }a
    !
    private void submitReport(Report report, File logs) {
    // TODO: Create email intent and set recipient.
    // TODO: Pre-fill body with device and report information.
    // TODO: Add screenshot and log file as attachment.
    }
    }aa

    View Slide

  165. debug_activity_frame (src/internalDebug)


    android:id="@+id/debug_content"
    />

    android:id="@+id/debug_drawer"

    android:layout_gravity="right"
    android:background="#ee212121"
    />


    View Slide

  166. debug_activity_frame (src/internalDebug)


    android:id="@+id/debug_content"
    />

    android:id="@+id/debug_drawer"

    android:layout_gravity="right"
    android:background="#ee212121"
    />


    View Slide

  167. debug_activity_frame (src/internalDebug)


    android:id="@+id/debug_content"
    />

    android:id="@+id/debug_drawer"

    android:layout_gravity="right"
    android:background="#ee212121"
    />


    View Slide

  168. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    activity.setContentView(R.layout.debug_activity_frame);
    !
    ViewGroup drawer = findById(activity, R.id.debug_drawer);

    DebugView debugView = new DebugView(activity);

    drawer.addView(debugView);
    !
    return findById(activity, R.id.debug_content);
    }a
    }a

    View Slide

  169. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    // ...
    !
    return findById(activity, R.id.debug_content);
    }a
    }a

    View Slide

  170. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    !
    @Inject public DebugAppContainer() { }
    !
    @Override public ViewGroup bind(final Activity activity) {
    // ...
    !
    TelescopeLayout telescopeLayout = findById(activity, R.id.debug_content);
    return telescopeLayout;
    }a
    }a

    View Slide

  171. DebugAppContainer (src/internalDebug)
    @Singleton

    public final class DebugAppContainer implements AppContainer {
    private final LumberYard lumberYard;
    !
    @Inject public DebugAppContainer(LumberYard lumberYard) {
    this.lumberYard = lumberYard;
    }a
    !
    @Override public ViewGroup bind(final Activity activity) {
    // ...
    !
    TelescopeLayout telescopeLayout = findById(activity, R.id.debug_content);
    telescopeLayout.setLens(new BugReportLens(activity, lumberYard));
    return telescopeLayout;
    }a
    }a

    View Slide

  172. internal_activity_frame (src/internalRelease)
    android:id="@+id/telescope_container"
    />

    View Slide

  173. TelescopeAppContainer (src/internalRelease)
    @Singleton

    public final class TelescopeAppContainer implements AppContainer {
    @InjectView(R.id.telescope_container) TelescopeLayout telescopeLayout;
    !
    private final LumberYard lumberYard;
    !
    @Inject public TelescopeAppContainer(LumberYard lumberYard) {

    this.lumberYard = lumberYard;

    }a
    !
    @Override public ViewGroup bind(final Activity activity) {

    activity.setContentView(R.layout.internal_activity_frame);

    ButterKnife.inject(this, activity);


    telescopeLayout.setLens(new BugReportLens(activity, lumberYard));
    return telescopeLayout;
    }a
    }a

    View Slide

  174. TelescopeAppContainer (src/internalRelease)
    @Singleton

    public final class TelescopeAppContainer implements AppContainer {
    @InjectView(R.id.telescope_container) TelescopeLayout telescopeLayout;
    !
    private final LumberYard lumberYard;
    !
    @Inject public TelescopeAppContainer(LumberYard lumberYard) {

    this.lumberYard = lumberYard;

    }a
    !
    @Override public ViewGroup bind(final Activity activity) {

    activity.setContentView(R.layout.internal_activity_frame);

    ButterKnife.inject(this, activity);


    telescopeLayout.setLens(new BugReportLens(activity, lumberYard));
    return telescopeLayout;
    }a
    }a

    View Slide

  175. TelescopeAppContainer (src/internalRelease)
    @Singleton

    public final class TelescopeAppContainer implements AppContainer {
    @InjectView(R.id.telescope_container) TelescopeLayout telescopeLayout;
    !
    private final LumberYard lumberYard;
    !
    @Inject public TelescopeAppContainer(LumberYard lumberYard) {

    this.lumberYard = lumberYard;

    }a
    !
    @Override public ViewGroup bind(final Activity activity) {

    activity.setContentView(R.layout.internal_activity_frame);

    ButterKnife.inject(this, activity);


    telescopeLayout.setLens(new BugReportLens(activity, lumberYard));
    return telescopeLayout;
    }a
    }a

    View Slide

  176. InternalReleaseUiModule (src/internalRelease)
    @Provides @Singleton AppContainer provideAppContainer(

    TelescopeAppContainer telescopeAppContainer) {

    return telescopeAppContainer;

    }

    View Slide

  177. Some more tools

    View Slide

  178. Some more tools
    • Mock push notifications

    View Slide

  179. Some more tools
    • Mock push notifications
    • Reset flags (finished on-boarding, seen help windows, etc.)

    View Slide

  180. Some more tools
    • Mock push notifications
    • Reset flags (finished on-boarding, seen help windows, etc.)
    • Shortcuts

    View Slide

  181. Some more tools
    • Mock push notifications
    • Reset flags (finished on-boarding, seen help windows, etc.)
    • Shortcuts
    • DB operations

    View Slide

  182. Some more tools
    • Mock push notifications
    • Reset flags (finished on-boarding, seen help windows, etc.)
    • Shortcuts
    • DB operations
    • Show logs

    View Slide

  183. Some more tools
    • Mock push notifications
    • Reset flags (finished on-boarding, seen help windows, etc.)
    • Shortcuts
    • DB operations
    • Show logs
    • Debug activity

    View Slide

  184. Some more tools
    • Mock push notifications
    • Reset flags (finished on-boarding, seen help windows, etc.)
    • Shortcuts
    • DB operations
    • Show logs
    • Debug activity
    • Contextual actions

    View Slide

  185. View Slide

  186. View Slide

  187. Final thoughts

    View Slide

  188. Final thoughts
    • Fork U+2020!

    View Slide

  189. Final thoughts
    • Fork U+2020!
    • Your time is valuable

    View Slide

  190. Final thoughts
    • Fork U+2020!
    • Your time is valuable
    • Add debug controls!

    View Slide

  191. Debug Builds
    A NEW HOPE
    Matt Precious

    View Slide

  192. Debug Builds
    A NEW HOPE
    Questions?
    Matt Precious
    @mattprec
    +MatthewPrecious

    View Slide