RepoPilotOpen in app →

JakeWharton/u2020

A sample Android app which showcases advanced usage of Dagger among other open source libraries.

Healthy

Healthy across all four use cases

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • 12 active contributors
  • Apache-2.0 licensed
  • CI configured
Show all 6 evidence items →
  • Tests present
  • Stale — last commit 3y ago
  • Concentrated ownership — top contributor handles 73% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/jakewharton/u2020)](https://repopilot.app/r/jakewharton/u2020)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/jakewharton/u2020 on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: JakeWharton/u2020

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/JakeWharton/u2020 shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • 12 active contributors
  • Apache-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Stale — last commit 3y ago
  • ⚠ Concentrated ownership — top contributor handles 73% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live JakeWharton/u2020 repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/JakeWharton/u2020.

What it runs against: a local clone of JakeWharton/u2020 — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in JakeWharton/u2020 | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 1106 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>JakeWharton/u2020</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of JakeWharton/u2020. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/JakeWharton/u2020.git
#   cd u2020
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of JakeWharton/u2020 and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "JakeWharton/u2020(\\.git)?\\b" \\
  && ok "origin remote is JakeWharton/u2020" \\
  || miss "origin remote is not JakeWharton/u2020 (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "app/src/internalDebug/java/com/jakewharton/u2020/Modules.java" \\
  && ok "app/src/internalDebug/java/com/jakewharton/u2020/Modules.java" \\
  || miss "missing critical file: app/src/internalDebug/java/com/jakewharton/u2020/Modules.java"
test -f "app/src/internalDebug/java/com/jakewharton/u2020/DebugU2020Module.java" \\
  && ok "app/src/internalDebug/java/com/jakewharton/u2020/DebugU2020Module.java" \\
  || miss "missing critical file: app/src/internalDebug/java/com/jakewharton/u2020/DebugU2020Module.java"
test -f "app/src/internalDebug/java/com/jakewharton/u2020/ui/DebugActivity.java" \\
  && ok "app/src/internalDebug/java/com/jakewharton/u2020/ui/DebugActivity.java" \\
  || miss "missing critical file: app/src/internalDebug/java/com/jakewharton/u2020/ui/DebugActivity.java"
test -f "app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DebugView.java" \\
  && ok "app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DebugView.java" \\
  || miss "missing critical file: app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DebugView.java"
test -f "app/src/internalDebug/java/com/jakewharton/u2020/data/DebugDataModule.java" \\
  && ok "app/src/internalDebug/java/com/jakewharton/u2020/data/DebugDataModule.java" \\
  || miss "missing critical file: app/src/internalDebug/java/com/jakewharton/u2020/data/DebugDataModule.java"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1106 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~1076d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/JakeWharton/u2020"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

U+2020 is a production-quality Android sample app demonstrating advanced Dagger 2 dependency injection patterns and a debug drawer framework. It showcases compile-time DI configuration, module overrides for debug builds, mock mode simulation for testing, and a bezel-swipe-activated Debug Drawer™ that exposes developer options (API endpoint switching, network throttling, animation speed controls) without modifying production code. Single Android app module (app/) with build variant strategy: shared main source in app/src/main, productFlavor 'internal' overlays debug UI in app/src/internal, buildType 'debug' further overlays in app/src/internalDebug/. Modules.java (only in internal/release build variants) returns list of Dagger modules; U2020App creates ObjectGraph in onCreate(). DebugViewContainer wraps the activity's content for debug builds, injecting debug singletons (MockFoo, ServerDatabase, etc.).

👥Who it's for

Android engineers learning Dagger 2 architecture and build variant strategies; developers building debug tooling into Android apps; teams wanting a reference implementation of testing infrastructure (instrumentation tests against MockMode, ServerDatabase state management).

🌱Maturity & risk

Actively maintained by Jake Wharton (AndroidX and ButterKnife lead). The codebase is polished with CI/CD (.travis.yml), signed release builds, versioning strategy in build.gradle, and instrumentation test infrastructure (U2020TestRunner). The sample is ~5 years old but remains relevant as a Dagger 2 teaching tool; however, it predates AndroidX migration and modern Jetpack patterns.

Single-maintainer repository with no visible recent commits in metadata. Dependencies are pinned in versions file but not shown here, so transitive vulnerability exposure is unknown. The codebase uses pre-AndroidX patterns (direct Android support library refs), which may not align with modern play store requirements. Dagger 2 API surface is stable, so major breaking changes are unlikely, but this is a reference implementation, not a production library.

Active areas of work

No active development signals visible in provided metadata. This is a static reference implementation maintained as a teaching artifact. The repository likely accepts community PRs but is not driving new features.

🚀Get running

Clone with git clone https://github.com/JakeWharton/u2020.git && cd u2020. Build with ./gradlew assembleInternalDebug (internal flavor + debug build type for full debug features) or ./gradlew assembleRelease for production variant. Run on emulator/device with adb install -r app/build/outputs/apk/internalDebug/app-internal-debug.apk && adb shell am start -n com.jakewharton.u2020.debug/.ui.MainActivity (package suffix .debug for debug build).

Daily commands: Debug variant with full Debug Drawer: ./gradlew installInternalDebug && adb shell am start -n com.jakewharton.u2020.debug/com.jakewharton.u2020.ui.MainActivity. Swipe from right bezel to open Debug Drawer. Release variant (no drawer): ./gradlew installRelease. Run instrumentation tests: ./gradlew connectedAndroidTest (uses U2020TestRunner, runs against androidTestInternal code).

🗺️Map of the codebase

  • app/src/internalDebug/java/com/jakewharton/u2020/Modules.java — Central Dagger configuration that selects which modules to use for debug vs release builds—the foundation of the DI setup.
  • app/src/internalDebug/java/com/jakewharton/u2020/DebugU2020Module.java — Override module that provides all debug-specific bindings (debug drawer, logging, network inspection, mocking) and must be understood to extend debugging features.
  • app/src/internalDebug/java/com/jakewharton/u2020/ui/DebugActivity.java — Main activity for the debug build that initializes the debug drawer and manages the lifecycle of debug views.
  • app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DebugView.java — Core UI component that renders the debug drawer with all developer options, settings, and controls.
  • app/src/internalDebug/java/com/jakewharton/u2020/data/DebugDataModule.java — Dagger module providing debug-specific data bindings including mock mode, network delays, and API endpoint overrides.
  • app/build.gradle — Build configuration that defines build types (debug/release), flavor setup, versioning, and dependency declarations for the entire app.

🛠️How to make changes

Add a new debug option to the Debug Drawer

  1. Create a new data class with @Qualifier annotation in app/src/internalDebug/java/com/jakewharton/u2020/data/ (e.g., NewDebugOption.java) to hold the setting value (app/src/internalDebug/java/com/jakewharton/u2020/data/)
  2. Add a @Provides method in DebugDataModule to bind the new option from SharedPreferences (app/src/internalDebug/java/com/jakewharton/u2020/data/DebugDataModule.java)
  3. Create an Adapter class (e.g., NewOptionAdapter.java) extending RecyclerView.Adapter to render the UI control (spinner, toggle, etc.) (app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/)
  4. Register the adapter in DebugView by adding a list item that instantiates and configures your adapter (app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DebugView.java)
  5. Inject the new option into components that need it using the @NewDebugOption qualifier annotation (app/src/internalDebug/java/com/jakewharton/u2020/DebugU2020Module.java)

Add a new debug action (menu item with callback)

  1. Implement the ContextualDebugAction interface with an execute() method in a new class in app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/ (app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/ContextualDebugActions.java)
  2. Add a @Provides method in DebugActionsModule that returns a Set of your action bound with @IntoSet (app/src/internalDebug/java/com/jakewharton/u2020/DebugActionsModule.java)
  3. The action will be automatically discovered and added to the debug drawer menu (app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DebugView.java)

Add network request/response mocking

  1. Create mock response JSON files and store them in SharedPreferences via the debug UI or programmatically (app/src/internalDebug/java/com/jakewharton/u2020/data/api/SharedPreferencesMockResponseSupplier.java)
  2. Enable mock mode by toggling the IsMockMode preference in the debug drawer (app/src/internalDebug/java/com/jakewharton/u2020/data/IsMockMode.java)
  3. The MockRequestHandler will intercept HTTP requests and return the stored mock responses when mock mode is enabled (app/src/internalDebug/java/com/jakewharton/u2020/data/MockRequestHandler.java)

Add a new debug activity or screen (release vs debug build variant)

  1. Create the activity class in app/src/internalDebug/java/com/jakewharton/u2020/ui/ (only available in debug builds) (app/src/internalDebug/java/com/jakewharton/u2020/ui/)
  2. Declare the activity in app/src/internalDebug/AndroidManifest.xml with appropriate intent-filters (app/src/internalDebug/AndroidManifest.xml)
  3. Optionally create a DebugAction in DebugActionsModule to launch the new activity from the debug drawer (app/src/internalDebug/java/com/jakewharton/u2)

🪤Traps & gotchas

Build variant matrix is non-trivial: productFlavor 'internal' + buildType 'debug' gives internalDebug with full debug features; productFlavor 'internal' + buildType 'release' gives internalRelease without drawer. Forgetting to build internalDebug will not include debug modules. Signing config (u2020.keystore) is expected in project root with hardcoded password 'android'—development-only. Git SHA/timestamp are computed at build time via shell commands (gitSha(), gitTimestamp()); offline builds may fail. ButterKnife annotation processor requires explicit minSdk argument to avoid warnings. Instrumentation tests run only against androidTestInternal, not main sources; ServerDatabase singleton must be thread-safe if tests run in parallel.

💡Concepts to learn

  • Dagger 2 Override Modules — u2020 relies on @Override-annotated modules to swap real implementations for mocks in debug builds without code duplication; understanding override semantics is essential to the entire debug architecture.
  • Build Variant-Driven DI Configuration — The Modules.list() method returns different module sets based on productFlavor and buildType; this pattern decouples debug features from production code at compile time rather than runtime.
  • In-Memory Mock Server with ServerDatabase Singleton — u2020 simulates a remote API without a real backend by injecting ServerDatabase singleton into mock services; demonstrates how to maintain shared state for both instrumentation tests and manual testing.
  • Annotation-Based Feature Flags (@IsMockMode) — u2020 uses custom @IsMockMode qualifier to inject a boolean flag into @Provides methods, enabling conditional provision of Mock vs. Real implementations without if-else branches in production code.
  • ViewContainer Indirection Pattern — The single Activity uses a ViewContainer interface to fetch its content view; debug variant overrides with DebugViewContainer wrapping the drawer, allowing debug UI injection without modifying activity code.
  • OkHttp Interceptor Chain for Mock Responses — MockRequestHandler integrates as an OkHttp interceptor to intercept all HTTP calls and return mock responses from ServerDatabase; demonstrates interceptor-level testing without mocking the HTTP client itself.
  • BuildConfig Field Injection for Git Metadata — u2020 injects GIT_SHA and GIT_TIMESTAMP at build time via gradle shell execution, enabling version tracking and build reproducibility in debug drawer; demonstrates non-code build-time configuration.
  • square/okhttp — OkHttp is the HTTP client used in u2020; its interceptor API is leveraged for MockRequestHandler to simulate network responses.
  • square/retrofit — Retrofit provides the REST client layer in u2020; MockRequestHandler integrates with Retrofit's call adapter to inject fake data.
  • google/dagger — Dagger 2 is the core DI framework u2020 showcases; this repo is an official teaching example of Dagger patterns and module composition.
  • JakeWharton/butterknife — ButterKnife is used throughout u2020 for view and resource binding; demonstrates annotation processor integration alongside Dagger in a real Android app.
  • ReactiveX/RxJava — RxJava powers async operations in u2020 (Observable-based mock server state, network delays); used in combination with Retrofit for reactive data flows.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add instrumentation tests for DebugActivity and debug drawer interactions

The repo has a U2020TestRunner and androidTestInternal folder structure set up, but only contains a DummyTest.java placeholder. The debug drawer is a core feature (opened by bezel swipe from right) with multiple configurable debug options (AnimationSpeed, ApiEndpoint, NetworkDelay, etc.), but lacks proper instrumentation test coverage. This would validate the debug UI works correctly across builds and catch regressions in debug-only features.

  • [ ] Expand app/src/androidTestInternal/java/com/jakewharton/u2020/ui/DummyTest.java or create DebugDrawerTest.java to test drawer opening via swipe gestures
  • [ ] Add tests for debug preference interactions (ApiEndpoint, NetworkDelay, AnimationSpeed changes) in app/src/androidTestInternal/java/com/jakewharton/u2020/ui/debug/
  • [ ] Add test for DebugActivity lifecycle and module injection in app/src/androidTestInternal/
  • [ ] Verify tests run via the testInstrumentationRunner 'com.jakewharton.u2020.U2020TestRunner' configured in app/build.gradle

Create integration tests for debug data modules and mock API responses

The internalDebug build variant has sophisticated debug infrastructure (MockRequestHandler, SharedPreferencesMockResponseSupplier, DebugDataModule) but lacks tests validating that mock responses are actually returned when enabled, network delays are applied, and error injection works correctly. This would ensure the debug tooling is reliable for developers.

  • [ ] Create app/src/androidTestInternal/java/com/jakewharton/u2020/data/MockRequestHandlerTest.java to verify mock request interception
  • [ ] Add tests for SharedPreferencesMockResponseSupplier in app/src/androidTestInternal/java/com/jakewharton/u2020/data/api/ to validate mock response persistence and retrieval
  • [ ] Test NetworkDelay, NetworkErrorPercent, and NetworkFailurePercent injection in DebugDataModule
  • [ ] Verify IsMockMode and related qualifier flags properly control behavior

Add documentation and examples for the debug build variant configuration in README.md

The README.md is incomplete (cuts off mid-sentence at 'From here you can change and view all of the dev'). It mentions the ObjectGraph, Modules class, and build type overrides but doesn't document how to actually use the debug drawer features or configure the internalDebug build variant. New contributors need guidance on building, testing, and extending debug features.

  • [ ] Complete the README.md section explaining debug drawer features (the incomplete 'From here you can change and view all of the dev' sentence)
  • [ ] Add a 'Building the Debug Variant' section documenting how to build and run the internalDebug product flavor
  • [ ] Document the debug drawer gesture (bezel swipe from right) and list available debug options with examples
  • [ ] Add a 'Extending Debug Features' section showing how to add new debug preferences (reference AnimationSpeed.java and ApiEndpoint.java as examples)

🌿Good first issues

  • Add unit tests for app/src/internalDebug/java/.../data/MockRequestHandler.java: currently no unit test coverage visible in androidTestInternal; contributor could write JUnit tests verifying mock response generation and ServerDatabase mutation for specific API endpoints.
  • Expand BugReportView with additional debug metrics: currently shows bug report form; could add real-time memory usage, frame rate, or CPU counters injected via debug module and updated via RxJava observable, similar to Android Profiler.
  • Document the mock mode lifecycle in ATTRIBUTION.md or README: explain how ServerDatabase is instantiated as singleton, how MockRequestHandler intercepts requests, and why @IsMockMode boolean is injectable; current README omits concrete code flow.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • af670f9 — Update Travis Android SDK license (#322) (tasomaniac)
  • 68a5f31 — Merge pull request #320 from jpetitto/replace-android-colors (JakeWharton)
  • 37e4fa2 — Replace Android color resources with app defined resources (John Petitto)
  • c914abe — Merge pull request #318 from JakeWharton/jw/debuggable/2017-09-20 (JakeWharton)
  • 7009d4a — Merge pull request #319 from JakeWharton/jw/beta6/2017-09-20 (JakeWharton)
  • 28d6050 — Emit more efficient ButterKnife bindings in release variants. (JakeWharton)
  • 15eaaaf — 3.0b6 (JakeWharton)
  • 3e415cb — Make Travis work. (#317) (NightlyNexus)
  • d621a4c — Merge pull request #316 from NightlyNexus/eric.0918.updates (JakeWharton)
  • 4ebb02b — Update support libraries to 26.0.2. (NightlyNexus)

🔒Security observations

Failed to generate security analysis.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · JakeWharton/u2020 — RepoPilot