Slide 1

Slide 1 text

Debug Builds A NEW HOPE Matt Precious

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

What we’ll need • Gradle build variants • Dagger (v1) • Retrofit (v2) • OkHttp • Timber

Slide 4

Slide 4 text

Build configuration

Slide 5

Slide 5 text

Build configuration • Types • debug • release

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Debug drawer

Slide 9

Slide 9 text

Debug drawer

Slide 10

Slide 10 text

MainActivity (src/main) public final class MainActivity extends Activity {
 
 @Override protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); setContentView(R.layout.main_activity);
 }
 }a

Slide 11

Slide 11 text

Activity Content Container Content

Slide 12

Slide 12 text

Activity Content Container Content Debug Drawer Debug View

Slide 13

Slide 13 text

AppContainer (src/main) public interface AppContainer {
 ViewGroup bind(Activity activity);
 }a

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

UiModule (src/main) @Provides @Singleton AppContainer provideAppContainer() {
 return AppContainer.DEFAULT;
 }a

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

debug_activity_frame (src/internalDebug) 
 
 


Slide 22

Slide 22 text

debug_activity_frame (src/internalDebug) 
 
 


Slide 23

Slide 23 text

debug_activity_frame (src/internalDebug) 
 
 


Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

DebugAppContainer (src/internalDebug) @Singleton
 public final class DebugAppContainer implements AppContainer { @Inject public DebugAppContainer() { } @Override public ViewGroup bind(final Activity activity) { }a }a

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

DebugUiModule (src/internalDebug) @Provides @Singleton AppContainer provideAppContainer(DebugAppContainer debugAppContainer) {
 return debugAppContainer;
 }a

Slide 31

Slide 31 text

Endpoints

Slide 32

Slide 32 text

Endpoints

Slide 33

Slide 33 text

ApiModule (src/main) public static final HttpUrl PRODUCTION_API_URL = HttpUrl.parse("https://api.github.com");
 
 @Provides @Singleton HttpUrl provideBaseUrl() {
 return PRODUCTION_API_URL;
 }

Slide 34

Slide 34 text

ApiEndpoints (src/internalDebug) public enum ApiEndpoints {
 PRODUCTION,
 STAGING,
 CUSTOM,
 }aa

Slide 35

Slide 35 text

ApiEndpoints (src/internalDebug) public enum ApiEndpoints {
 PRODUCTION("Production", ApiModule.PRODUCTION_API_URL.toString()),
 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

Slide 36

Slide 36 text

DebugDataModule (src/internalDebug) @Provides @Singleton @ApiEndpoint
 StringPreference provideEndpointPreference(SharedPreferences preferences) {
 return new StringPreference(preferences, “debug_endpoint", ApiEndpoints.STAGING.url);
 }

Slide 37

Slide 37 text

DebugApiModule (src/internalDebug) @Provides @Singleton
 HttpUrl provideBaseUrl(@ApiEndpoint StringPreference apiEndpoint) {
 return HttpUrl.parse(apiEndpoint.get());
 }

Slide 38

Slide 38 text

debug_view_content (src/internalDebug)

Slide 39

Slide 39 text

DebugView (src/internalDebug) private void setupNetworkSection() {
 }a

Slide 40

Slide 40 text

DebugView (src/internalDebug) @Inject @ApiEndpoint StringPreference networkEndpoint; private void setupNetworkSection() {
 final ApiEndpoints currentEndpoint = ApiEndpoints.from(networkEndpoint.get());
 }aa

Slide 41

Slide 41 text

DebugView (src/internalDebug) @Bind(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

Slide 42

Slide 42 text

DebugView (src/internalDebug) @Bind(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

Slide 43

Slide 43 text

DebugView (src/internalDebug) @Bind(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

Slide 44

Slide 44 text

DebugView (src/internalDebug) @Bind(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

Slide 45

Slide 45 text

DebugView (src/internalDebug) @Bind(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

Slide 46

Slide 46 text

DebugView (src/internalDebug) @Bind(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

Slide 47

Slide 47 text

DebugView (src/internalDebug) @Inject @ApiEndpoint StringPreference networkEndpoint; private void setupNetworkSection() {
 // ...
 }aa private void setEndpointAndRelaunch(String endpoint) {
 networkEndpoint.set(endpoint);
 
 ProcessPhoenix.triggerRebirth(getContext());
 }a

Slide 48

Slide 48 text

DebugView (src/internalDebug) @Inject @ApiEndpoint StringPreference networkEndpoint; private void setupNetworkSection() {
 // ...
 }aa private void setEndpointAndRelaunch(String endpoint) {
 networkEndpoint.set(endpoint);
 
 ProcessPhoenix.triggerRebirth(getContext());
 }a https://github.com/JakeWharton/ProcessPhoenix

Slide 49

Slide 49 text

Network Proxy

Slide 50

Slide 50 text

Network Proxy

Slide 51

Slide 51 text

Network Proxy

Slide 52

Slide 52 text

DataModule (src/main) @Provides @Singleton OkHttpClient provideOkHttpClient(Application app) {
 return createOkHttpClient(app);
 } static OkHttpClient createOkHttpClient(Application app) {
 OkHttpClient client = new OkHttpClient();
 // ... 
 return client;
 }

Slide 53

Slide 53 text

NetworkProxyPreference (src/internalDebug) public final class NetworkProxyPreference extends StringPreference {
 public NetworkProxyPreference(SharedPreferences preferences, String key) {
 super(preferences, key);
 }
 
 public @Nullable Proxy getProxy() {
 // TODO: Convert String value to Proxy and return.
 }
 }

Slide 54

Slide 54 text

DebugDataModule (src/internalDebug) @Provides @Singleton OkHttpClient provideOkHttpClient(Application app) {
 OkHttpClient client = DataModule.createOkHttpClient(app);
 return client;
 }a

Slide 55

Slide 55 text

DebugDataModule (src/internalDebug) @Provides @Singleton OkHttpClient provideOkHttpClient(Application app,
 NetworkProxyPreference networkProxy) {
 OkHttpClient client = DataModule.createOkHttpClient(app); 
 client.setProxy(networkProxy.getProxy());
 return client;
 }a

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

debug_view_content (src/internalDebug) 


Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

DebugView (src/internalDebug) @Bind(R.id.debug_network_proxy) Spinner networkProxyView; @Inject NetworkProxyPreference networkProxy; private void setupNetworkSection() { // ... 
 final ProxyAdapter proxyAdapter = new ProxyAdapter(getContext(), networkProxy);
 networkProxyView.setAdapter(proxyAdapter);
 }aa

Slide 60

Slide 60 text

DebugView (src/internalDebug) @Bind(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

Slide 61

Slide 61 text

DebugView (src/internalDebug) @Bind(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

Slide 62

Slide 62 text

DebugView (src/internalDebug) @Bind(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

Slide 63

Slide 63 text

DebugView (src/internalDebug) @Bind(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

Slide 64

Slide 64 text

DebugView (src/internalDebug) @Bind(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);
 }

Slide 65

Slide 65 text

Mock Mode

Slide 66

Slide 66 text

GithubService (src/main) public interface GithubService {
 @GET("/search/repositories") //
 Observable> repositories(
 @Query("q") SearchQuery query,
 @Query("sort") Sort sort,
 @Query("order") Order order);
 }

Slide 67

Slide 67 text

ApiModule (src/main) @Provides @Singleton GithubService provideGithubService(Retrofit retrofit) {
 return retrofit.create(GithubService.class);
 }

Slide 68

Slide 68 text

ApiEndpoints (src/internalDebug) public enum ApiEndpoints {
 PRODUCTION("Production", ApiModule.PRODUCTION_API_URL.toString()),
 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

Slide 69

Slide 69 text

ApiEndpoints (src/internalDebug) public enum ApiEndpoints {
 PRODUCTION("Production", ApiModule.PRODUCTION_API_URL.toString()),
 STAGING("Staging", "https://api.staging.github.com/"), MOCK_MODE("Mock mode", "http://localhost/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

Slide 70

Slide 70 text

ApiEndpoints (src/internalDebug) public enum ApiEndpoints {
 // ...
 public static boolean isMockMode(String endpoint) {
 return from(endpoint) == MOCK_MODE;
 }a }aa

Slide 71

Slide 71 text

DebugDataModule (src/internalDebug) @Provides @Singleton @IsMockMode boolean provideIsMockMode(@ApiEndpoint StringPreference endpoint) {
 return ApiEndpoints.isMockMode(endpoint.get());
 }

Slide 72

Slide 72 text

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(); // ...

Slide 73

Slide 73 text

MockGithubService (src/internalDebug) @Singleton
 public final class MockGithubService implements GithubService {
 
 @Inject MockGithubService() { }
 }a

Slide 74

Slide 74 text

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(Result.response(Response.success( new RepositoriesResponse(Arrays.asList(
 BUTTERKNIFE,
 DAGGER,
 OKHTTP,
 OKIO,
 PICASSO,
 RETROFIT,
 TELESCOPE,
 U2020)))));
 }
 }a

Slide 75

Slide 75 text

DebugApiModule (src/internalDebug) @Provides @Singleton
 GithubService provideGithubService(Retrofit retrofit, MockRetrofit mockRetrofit, @IsMockMode boolean isMockMode, MockGithubService mockService) {
 if (isMockMode) {
 return mockRetrofit.create(GithubService.class, mockService);
 } 
 return retrofit.create(GithubService.class);
 }

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

:)

Slide 78

Slide 78 text

:) Loading Error Empty One result Lots of results

Slide 79

Slide 79 text

:) Loading Error Empty One result Lots of results ✔

Slide 80

Slide 80 text

:) Loading Error Empty One result Lots of results ✔

Slide 81

Slide 81 text

debug_view_content (src/internalDebug) 
 
 
 


Slide 82

Slide 82 text

DebugApiModule (src/internalDebug) @Provides @Singleton NetworkBehavior provideNetworkBehavior(/* preferences */) { NetworkBehavior behavior = NetworkBehavior.create(); // TODO: Restore behavior values from preferences. return behavior; } @Provides @Singleton MockRetrofit provideMockRestAdapter(NetworkBehavior behavior) {
 return new MockRetrofit(behavior, RxJavaBehaviorAdapter.create());
 }

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

DebugView (src/internalDebug) @InjectView(R.id.debug_network_delay) Spinner networkDelayView; @Inject NetworkBehavior behavior; private void setupNetworkSection() { // ... final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());
 networkDelayView.setAdapter(delayAdapter);
 networkDelayView.setSelection(
 NetworkDelayAdapter.getPositionForValue(behavior.delay(MILLISECONDS))); }aa

Slide 86

Slide 86 text

DebugView (src/internalDebug) @InjectView(R.id.debug_network_delay) Spinner networkDelayView; @Inject NetworkBehavior behavior; private void setupNetworkSection() { // ... final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());
 networkDelayView.setAdapter(delayAdapter);
 networkDelayView.setSelection(
 NetworkDelayAdapter.getPositionForValue(behavior.delay(MILLISECONDS))); }aa

Slide 87

Slide 87 text

DebugView (src/internalDebug) @InjectView(R.id.debug_network_delay) Spinner networkDelayView; @Inject NetworkBehavior behavior; @Inject @NetworkDelay LongPreference networkDelay; private void setupNetworkSection() { // ... final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());
 networkDelayView.setAdapter(delayAdapter);
 networkDelayView.setSelection(
 NetworkDelayAdapter.getPositionForValue(behavior.delay(MILLISECONDS)));
 networkDelayView.setOnItemSelectedListener((adapterView, view, position, id) -> {
 int selected = delayAdapter.getItem(position);
 if (selected != behavior.delay(MILLISECONDS)) {
 behavior.setDelay(selected, MILLISECONDS); networkDelay.set(selected);
 }a
 }); }aa

Slide 88

Slide 88 text

DebugView (src/internalDebug) @InjectView(R.id.debug_network_delay) Spinner networkDelayView; @Inject NetworkBehavior behavior; @Inject @NetworkDelay LongPreference networkDelay; private void setupNetworkSection() { // ... final NetworkDelayAdapter delayAdapter = new NetworkDelayAdapter(getContext());
 networkDelayView.setAdapter(delayAdapter);
 networkDelayView.setSelection(
 NetworkDelayAdapter.getPositionForValue(behavior.delay(MILLISECONDS)));
 networkDelayView.setOnItemSelectedListener((adapterView, view, position, id) -> {
 int selected = delayAdapter.getItem(position);
 if (selected != behavior.delay(MILLISECONDS)) {
 behavior.setDelay(selected, MILLISECONDS); networkDelay.set(selected);
 }a
 }); }aa

Slide 89

Slide 89 text

DebugView (src/internalDebug) @InjectView(R.id.debug_network_error) Spinner networkErrorView; @Inject NetworkBehavior behavior; @Inject @NetworkFailurePercent IntPreference networkFailurePercent; private void setupNetworkSection() { // ... final NetworkErrorAdapter errorAdapter = new NetworkErrorAdapter(getContext());
 networkErrorView.setAdapter(errorAdapter);
 networkErrorView.setSelection(
 NetworkErrorAdapter.getPositionForValue(behavior.failurePercent()));
 networkErrorView.setOnItemSelectedListener((adapterView, view, position, id) -> {
 int selected = errorAdapter.getItem(position);
 if (selected != behavior.failurePercent()) {
 behavior.setFailurePercent(selected); networkFailurePercent.set(selected);
 }a
 }); }aa

Slide 90

Slide 90 text

DebugView (src/internalDebug) @InjectView(R.id.debug_network_error) Spinner networkErrorView; @Inject NetworkBehavior behavior; @Inject @NetworkFailurePercent IntPreference networkFailurePercent; private void setupNetworkSection() { // ... final NetworkErrorAdapter errorAdapter = new NetworkErrorAdapter(getContext());
 networkErrorView.setAdapter(errorAdapter);
 networkErrorView.setSelection(
 NetworkErrorAdapter.getPositionForValue(behavior.failurePercent()));
 networkErrorView.setOnItemSelectedListener((adapterView, view, position, id) -> {
 int selected = errorAdapter.getItem(position);
 if (selected != behavior.failurePercent()) {
 behavior.setFailurePercent(selected); networkFailurePercent.set(selected);
 }a
 }); }aa

Slide 91

Slide 91 text

:) Loading Error Empty One result Lots of results ✔

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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(Result.response(Response.success( new RepositoriesResponse(Arrays.asList(
 BUTTERKNIFE,
 DAGGER,
 OKHTTP,
 OKIO,
 PICASSO,
 RETROFIT,
 TELESCOPE,
 U2020)))));
 }a
 }aa

Slide 94

Slide 94 text

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(Result.response(Response.success( new RepositoriesResponse(Arrays.asList(
 BUTTERKNIFE,
 DAGGER,
 OKHTTP,
 OKIO,
 PICASSO,
 RETROFIT,
 TELESCOPE,
 U2020)))));
 }a
 }aa

Slide 95

Slide 95 text

new RepositoriesResponse(Arrays.asList(
 BUTTERKNIFE,
 DAGGER,
 OKHTTP,
 OKIO,
 PICASSO,
 RETROFIT,
 TELESCOPE,
 U2020))

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

MockGithubService (src/internalDebug) @Singleton
 public final class MockGithubService implements GithubService {
 // ...
 }aa

Slide 99

Slide 99 text

MockGithubService (src/internalDebug) @Singleton
 public final class MockGithubService implements GithubService {
 // ...
 
 public > T getResponse(Class responseClass) { // } 
 public > void setResponse(Class responseClass, T value) { // }
 }aa

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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(Result.response(Response.success(response)));
 }a
 }aa

Slide 104

Slide 104 text

debug_view_content (src/internalDebug) 


Slide 105

Slide 105 text

DebugView (src/internalDebug) private > void configureResponseSpinner(Spinner spinner,
 final Class responseClass) {
 }aa

Slide 106

Slide 106 text

DebugView (src/internalDebug) private > void configureResponseSpinner(Spinner spinner,
 final Class responseClass) {
 }aa

Slide 107

Slide 107 text

DebugView (src/internalDebug) private > void configureResponseSpinner(Spinner spinner,
 final Class responseClass) {
 }aa

Slide 108

Slide 108 text

DebugView (src/internalDebug) private > void configureResponseSpinner(Spinner spinner,
 final Class responseClass) {
 final EnumAdapter adapter = new EnumAdapter<>(getContext(), responseClass); spinner.setAdapter(adapter); }aa

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

DebugView (src/internalDebug) @Bind(R.id.debug_repositories_response) Spinner repositoriesResponseView; private void setupMockBehaviorSection() {
 configureResponseSpinner(repositoriesResponseView, MockRepositoriesResponse.class);
 } private > void configureResponseSpinner(Spinner spinner,
 final Class responseClass) {
 // ...
 }aa

Slide 113

Slide 113 text

Intent Capturing

Slide 114

Slide 114 text

Intent Capturing

Slide 115

Slide 115 text

IntentFactory (src/main) public interface IntentFactory {
 }a

Slide 116

Slide 116 text

IntentFactory (src/main) public interface IntentFactory {
 Intent createUrlIntent(String url);
 }a

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

DataModule (src/main) @Provides @Singleton IntentFactory provideIntentFactory() {
 return IntentFactory.REAL;
 }

Slide 119

Slide 119 text

ExternalIntentActivity (src/internalDebug) public final class ExternalIntentActivity extends Activity {
 }a

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

DebugDataModule (src/internalDebug) @Provides @Singleton @CaptureIntents
 BooleanPreference provideCaptureIntentsPreference(SharedPreferences preferences) {
 return new BooleanPreference(preferences, "debug_capture_intents", DEFAULT_CAPTURE_INTENTS);
 }

Slide 125

Slide 125 text

DebugIntentFactory (src/internalDebug) @Singleton
 public final class DebugIntentFactory implements IntentFactory {
 
 @Inject public DebugIntentFactory() { }
 }aa

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

DebugDataModule (src/internalDebug) @Provides @Singleton IntentFactory provideIntentFactory(DebugIntentFactory debugIntentFactory) {
 return debugIntentFactory;
 }

Slide 131

Slide 131 text

Bug Reporting

Slide 132

Slide 132 text

Bug Reporting

Slide 133

Slide 133 text

Bug Reporting

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

debug_activity_frame (src/internalDebug) 
 
 


Slide 143

Slide 143 text

debug_activity_frame (src/internalDebug) 
 
 


Slide 144

Slide 144 text

debug_activity_frame (src/internalDebug) 
 
 


Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

internal_activity_frame (src/internalRelease)

Slide 150

Slide 150 text

TelescopeAppContainer (src/internalRelease) @Singleton
 public final class TelescopeAppContainer implements AppContainer { @Bind(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.bind(this, activity);
 
 telescopeLayout.setLens(new BugReportLens(activity, lumberYard)); return telescopeLayout; }a }a

Slide 151

Slide 151 text

TelescopeAppContainer (src/internalRelease) @Singleton
 public final class TelescopeAppContainer implements AppContainer { @Bind(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.bind(this, activity);
 
 telescopeLayout.setLens(new BugReportLens(activity, lumberYard)); return telescopeLayout; }a }a

Slide 152

Slide 152 text

TelescopeAppContainer (src/internalRelease) @Singleton
 public final class TelescopeAppContainer implements AppContainer { @Bind(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.bind(this, activity);
 
 telescopeLayout.setLens(new BugReportLens(activity, lumberYard)); return telescopeLayout; }a }a

Slide 153

Slide 153 text

InternalReleaseUiModule (src/internalRelease) @Provides @Singleton AppContainer provideAppContainer(
 TelescopeAppContainer telescopeAppContainer) {
 return telescopeAppContainer;
 }

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

No content

Slide 156

Slide 156 text

No content

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

Debug Builds A NEW HOPE Matt Precious

Slide 159

Slide 159 text

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