RepoPilotOpen in app →

KunMinX/Jetpack-MVVM-Best-Practice

难得一见 Jetpack MVVM 最佳实践。在 "以简驭繁" 代码中,对 "视图控制器" 乃至 "标准化开发模式" 形成正确、深入理解。

Concerns

Stale and unlicensed — last commit 2y ago

weakest axis
Use as dependencyConcerns

no license — legally unclear; last commit was 2y ago…

Fork & modifyConcerns

no license — can't legally use code; last commit was 2y ago

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isConcerns

no license — can't legally use code; last commit was 2y ago

  • 2 active contributors
  • CI configured
  • Tests present
Show all 7 evidence items →
  • Stale — last commit 2y ago
  • Small team — 2 contributors active in recent commits
  • Single-maintainer risk — top contributor 96% of recent commits
  • No license — legally unclear to depend on
What would change the summary?
  • Use as dependency ConcernsMixed if: publish a permissive license (MIT, Apache-2.0, etc.)
  • Fork & modify ConcernsMixed if: add a LICENSE file
  • Deploy as-is ConcernsMixed if: add a LICENSE file

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 "Great to learn from" badge

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

RepoPilot: Great to learn from
[![RepoPilot: Great to learn from](https://repopilot.app/api/badge/kunminx/jetpack-mvvm-best-practice?axis=learn)](https://repopilot.app/r/kunminx/jetpack-mvvm-best-practice)

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/kunminx/jetpack-mvvm-best-practice on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: KunMinX/Jetpack-MVVM-Best-Practice

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/KunMinX/Jetpack-MVVM-Best-Practice 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

AVOID — Stale and unlicensed — last commit 2y ago

  • 2 active contributors
  • CI configured
  • Tests present
  • ⚠ Stale — last commit 2y ago
  • ⚠ Small team — 2 contributors active in recent commits
  • ⚠ Single-maintainer risk — top contributor 96% of recent commits
  • ⚠ No license — legally unclear to depend on

<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 KunMinX/Jetpack-MVVM-Best-Practice repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/KunMinX/Jetpack-MVVM-Best-Practice.

What it runs against: a local clone of KunMinX/Jetpack-MVVM-Best-Practice — 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 KunMinX/Jetpack-MVVM-Best-Practice | Confirms the artifact applies here, not a fork | | 2 | Default branch master exists | Catches branch renames | | 3 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 4 | Last commit ≤ 850 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>KunMinX/Jetpack-MVVM-Best-Practice</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of KunMinX/Jetpack-MVVM-Best-Practice. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/KunMinX/Jetpack-MVVM-Best-Practice.git
#   cd Jetpack-MVVM-Best-Practice
#
# 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 KunMinX/Jetpack-MVVM-Best-Practice and re-run."
  exit 2
fi

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

# 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/main/java/com/kunminx/puremusic/MainActivity.java" \\
  && ok "app/src/main/java/com/kunminx/puremusic/MainActivity.java" \\
  || miss "missing critical file: app/src/main/java/com/kunminx/puremusic/MainActivity.java"
test -f "app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java" \\
  && ok "app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java" \\
  || miss "missing critical file: app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java"
test -f "app/src/main/java/com/kunminx/puremusic/data/repository/DataRepository.java" \\
  && ok "app/src/main/java/com/kunminx/puremusic/data/repository/DataRepository.java" \\
  || miss "missing critical file: app/src/main/java/com/kunminx/puremusic/data/repository/DataRepository.java"
test -f "app/src/main/java/com/kunminx/puremusic/domain/request/MusicRequester.java" \\
  && ok "app/src/main/java/com/kunminx/puremusic/domain/request/MusicRequester.java" \\
  || miss "missing critical file: app/src/main/java/com/kunminx/puremusic/domain/request/MusicRequester.java"
test -f "app/src/main/java/com/kunminx/puremusic/ui/page/PlayerFragment.java" \\
  && ok "app/src/main/java/com/kunminx/puremusic/ui/page/PlayerFragment.java" \\
  || miss "missing critical file: app/src/main/java/com/kunminx/puremusic/ui/page/PlayerFragment.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 850 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~820d)"
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/KunMinX/Jetpack-MVVM-Best-Practice"
  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

A production-grade Android MVVM reference implementation using Jetpack components (ViewModel, LiveData, Navigation, DataBinding) to demonstrate best practices for state management, lifecycle handling, and UI coordination. It's a music player app (PureMusic) that showcases seamless portrait/landscape layout switching, cross-fragment state sharing via SharedViewModel, and centralized data flow without boilerplate. Single-module Android app under app/src/main/java/com/kunminx/puremusic/ organized by clean architecture domains: data/ (API, beans, repository), domain/ (events, messaging, proxies, requesters), MainActivity.java + Fragments. Core architectural logic lives in domain/message/ (SharedViewModel, PageMessenger for cross-fragment communication) and domain/proxy/PlayerManager.java (player state proxy). DataBinding enabled via buildFeatures { dataBinding true } in app/build.gradle.

👥Who it's for

Android developers learning or applying Jetpack MVVM patterns, particularly those building multi-fragment apps with complex state synchronization needs. Team leads establishing standardized architecture patterns want to reference this codebase. KunMinX's course students ("重学安卓" paid subscribers) use this as the companion project.

🌱Maturity & risk

Active and production-ready. The repo shows consistent maintenance (CI/CD via GitHub Actions in .github/workflows/ci.yml), comprehensive test structure (androidTest/), and clear architectural discipline. No abandonment signals; KunMinX continues updating the companion course materials. Code is Java-based (272KB) with battle-tested patterns from his architectural consulting experience across 10+ mid/large-scale Android projects.

Low technical risk, but author-centric. Single maintainer (KunMinX) with copyright claims in README restricting commercial reuse. Dependencies are stable (Jetpack libraries, Retrofit via AccountService.java), but repo doesn't expose full dependency tree in snippet. No open issue backlog visible in provided data, suggesting either low adoption friction or limited community engagement.

Active areas of work

No PR/milestone data visible, but repo targets recent Android API levels (compileSdk/minSdk from gradle properties). Dependabot enabled (.github/dependabot.yml) suggests automated dependency updates. CI workflow (.github/workflows/ci.yml) is configured, indicating active CI/CD maintenance. Author actively maintains companion article series in linked "重学安卓" course.

🚀Get running

git clone https://github.com/KunMinX/Jetpack-MVVM-Best-Practice.git
cd Jetpack-MVVM-Best-Practice
./gradlew assembleDebug
./gradlew installDebug

Then open in Android Studio, sync Gradle, and run on emulator (API 21+) or device. No external service setup required; app bundles test music asset at app/src/main/assets/bensound-sunny.mp3.

Daily commands:

./gradlew connectedAndroidTest  # Run instrumented tests
./gradlew runDebugUnitTests      # Run local unit tests

For development: Load in Android Studio, sync Gradle, and use standard Run/Debug buttons. App auto-plays music on launch (asset-based, no network required).

🗺️Map of the codebase

  • app/src/main/java/com/kunminx/puremusic/MainActivity.java — Application entry point and root Activity that hosts all fragments and manages the primary navigation structure.
  • app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java — Core ViewModel demonstrating Jetpack MVVM shared state pattern between fragments; critical for understanding cross-fragment communication.
  • app/src/main/java/com/kunminx/puremusic/data/repository/DataRepository.java — Single source of truth for all data operations; bridges UI layer with API and local data sources.
  • app/src/main/java/com/kunminx/puremusic/domain/request/MusicRequester.java — Encapsulates music data fetching logic and request orchestration; demonstrates proper separation of concerns from UI.
  • app/src/main/java/com/kunminx/puremusic/ui/page/PlayerFragment.java — Primary UI Fragment showcasing data binding, ViewModel integration, and reactive state management in practice.
  • app/src/main/java/com/kunminx/puremusic/domain/proxy/PlayerManager.java — Proxy pattern implementation managing media player lifecycle; demonstrates abstraction of platform services.
  • app/build.gradle — Gradle build configuration defining all Jetpack dependencies, compilation targets, and build variants.

🛠️How to make changes

Add a New Music API Endpoint

  1. Define API method in AccountService interface with @GET, @POST annotations (app/src/main/java/com/kunminx/puremusic/data/api/AccountService.java)
  2. Add endpoint constant to APIs class (app/src/main/java/com/kunminx/puremusic/data/api/APIs.java)
  3. Create new Requester class (e.g., ArtistRequester) extending from MusicRequester pattern (app/src/main/java/com/kunminx/puremusic/domain/request/MusicRequester.java)
  4. Add data fetching method to DataRepository delegating to new Requester (app/src/main/java/com/kunminx/puremusic/data/repository/DataRepository.java)
  5. Expose LiveData or StateFlow property in SharedViewModel for Fragment subscription (app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java)

Add a New UI Fragment & Page

  1. Create new Fragment class extending androidx.fragment.app.Fragment with layout binding (app/src/main/java/com/kunminx/puremusic/ui/page/MainFragment.java)
  2. Create corresponding layout XML file with data binding variables (app/src/main/res/layout/fragment_main.xml)
  3. Obtain ViewModel instance via ViewModelProvider and observe LiveData (app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java)
  4. Register Fragment transaction in MainActivity's navigation flow (app/src/main/java/com/kunminx/puremusic/MainActivity.java)
  5. If list-based, create adapter extending RecyclerView.Adapter following PlaylistAdapter pattern (app/src/main/java/com/kunminx/puremusic/ui/page/adapter/PlaylistAdapter.java)

Implement a New Use Case or Business Logic

  1. Create new UseCase class with execute() method encapsulating business logic (app/src/main/java/com/kunminx/puremusic/domain/usecase/DownloadUseCase.java)
  2. Create corresponding Requester class to handle data/API calls (app/src/main/java/com/kunminx/puremusic/domain/request/DownloadRequester.java)
  3. Inject UseCase into DataRepository and expose via public method (app/src/main/java/com/kunminx/puremusic/data/repository/DataRepository.java)
  4. Expose result as LiveData/StateFlow property in SharedViewModel (app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java)
  5. Subscribe to result in target Fragment and update UI via data binding (app/src/main/java/com/kunminx/puremusic/ui/page/PlayerFragment.java)

Add Event Messaging Between Fragments

  1. Define event class in domain/event package (e.g., SearchEvent extending Messages) (app/src/main/java/com/kunminx/puremusic/domain/event/Messages.java)
  2. Add public LiveData<EventType> property in SharedViewModel for publishing events (app/src/main/java/com/kunminx/puremusic/domain/message/SharedViewModel.java)
  3. Post event from source Fragment via ViewModel method call (app/src/main/java/com/kunminx/puremusic/ui/page/SearchFragment.java)
  4. Subscribe and observe event in target Fragment's onViewCreated() method (app/src/main/java/com/kunminx/puremusic/ui/page/MainFragment.java)

🪤Traps & gotchas

No external service required — all data is mocked or asset-based (AccountService uses mock responses in tests). ProGuard rules applied: app/proguard-rules.pro must be maintained when adding new libraries; test aggressively post-minification. DataBinding overhead: Enabled globally via buildFeatures, which increases build time slightly. Asset music file: bensound-sunny.mp3 is bundled; if modified, ensure format compatibility with ExoPlayer. Fragment backstack: Navigation component handles this automatically, but custom back handling in MainActivity or Fragments can break state restoration — follow SharedViewModel patterns instead of manual SavedState. Orientation changes: Layout switching relies on Fragment recreation; if using custom SavedState, must wire it through ViewModel.mutableLiveData to persist across rotations.

🏗️Architecture

💡Concepts to learn

  • ViewModel Lifecycle & Scope — In this repo, ViewModel persists across Fragment recreation during config changes (rotation) while Fragment lifecycle resets; understanding this scope mismatch is critical to correctly implement state restoration without memory leaks.
  • LiveData Observer Pattern & Single-Source-of-Truth — SharedViewModel and PageMessenger in this repo use LiveData to broadcast state changes to multiple Fragments simultaneously; any observer can trigger UI updates without coupling Fragments to each other.
  • Fragment Backstack & Navigation Graph — Jetpack Navigation manages Fragment transactions and backstack automatically; this repo avoids FragmentManager.beginTransaction() boilerplate by delegating to NavController, reducing state loss bugs during back navigation.
  • DataBinding & Lifecycle-Aware Binding — This repo enables DataBinding globally; XML layouts directly observe ViewModel LiveData properties, eliminating findViewById() and manual observer registration while automatically unsubscribing when Fragment is destroyed.
  • Repository Pattern as Data Source Abstraction — DataRepository.java abstracts API calls, caching, and database access behind a single interface; allows ViewModel to request data without knowing whether it comes from network, cache, or local storage.
  • Proxy Pattern for Lifecycle-Sensitive Services — PlayerManager.java wraps ExoPlayer lifecycle; ensures playback resources (audio focus, callbacks) are tied to ViewModel lifecycle rather than Fragment, preventing orphaned players on config changes.
  • Configuration Change Handling & SavedState — This repo achieves seamless portrait/landscape layout switching via ViewModel persistence + LiveData restoration; manually managing SavedState or Bundle is error-prone and this pattern replaces it.
  • android/architecture-samples — Google's official MVVM/MVI reference implementations (Todo-MVP, Notes-MVVM); directly inspired many patterns in this repo but less opinionated on state flow.
  • googlecodelabs/android-navigation — Google Codelabs for Jetpack Navigation; this repo applies those Navigation patterns for inter-fragment routing and backstack handling.
  • KunMinX/UnPeek-LiveData — Companion library by the same author (KunMinX) that extends LiveData to prevent accidental re-delivery on config changes; often used alongside this codebase.
  • square/retrofit — HTTP client library used in AccountService.java; understanding Retrofit interceptors and observable responses is essential to extending data layer.
  • androidx/navigation — Jetpack Navigation framework upstream repo; source of truth for Navigation graph XML format and Fragment backstack semantics used throughout MainActivity.

🪄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 instrumented tests for ViewModel lifecycle and LiveData observation patterns

The repo demonstrates best practices for Jetpack MVVM but lacks comprehensive instrumented tests for the ViewModel and LiveData patterns used throughout the domain layer. Currently only ExampleInstrumentedTest.java exists. Adding tests for SharedViewModel, PlayerManager, and the various Requesters (AccountRequester, MusicRequester, DownloadRequester) would validate the architecture's core patterns and help contributors understand proper testing strategies for the recommended patterns.

  • [ ] Create app/src/androidTest/java/com/kunminx/puremusic/domain/ViewModelLifecycleTest.java to test SharedViewModel initialization and LiveData emissions
  • [ ] Add tests in app/src/androidTest/java/com/kunminx/puremusic/domain/request/ for AccountRequester, MusicRequester, and DownloadRequester to verify request handling and LiveData observation
  • [ ] Create app/src/androidTest/java/com/kunminx/puremusic/domain/proxy/PlayerManagerTest.java to test PlayerManager state management across Fragment lifecycle
  • [ ] Document testing patterns in README.md with a dedicated 'Testing Architecture' section referencing the new test files

Add unit tests for data binding adapters and their null-safety handling

The ui/bind/ directory contains 5 binding adapters (CommonBindingAdapter, DrawerBindingAdapter, IconBindingAdapter, TabPageBindingAdapter, WebViewBindingAdapter) but has no corresponding unit tests. These adapters are critical for the View layer and should have tests validating null-safety, edge cases, and proper resource binding to serve as reference implementations.

  • [ ] Create app/src/test/java/com/kunminx/puremusic/ui/bind/CommonBindingAdapterTest.java with tests for image loading, visibility binding, and null-safety
  • [ ] Create app/src/test/java/com/kunminx/puremusic/ui/bind/DrawerBindingAdapterTest.java testing drawer-specific binding logic
  • [ ] Create app/src/test/java/com/kunminx/puremusic/ui/bind/WebViewBindingAdapterTest.java validating WebView initialization edge cases
  • [ ] Update build.gradle to include testing dependencies (Robolectric if needed for binding tests)

Create comprehensive documentation for the architecture module dependency and establish module integration tests

The project imports a :architecture module (referenced in app/build.gradle as 'implementation project(":architecture")') but this module is not visible in the provided file structure. This is likely a critical foundation module containing base classes, but there's no documentation explaining module boundaries, public APIs, or integration points. Adding documentation and integration tests would help contributors understand the layered architecture properly.

  • [ ] Document the architecture module's purpose and public API in docs/ARCHITECTURE_MODULE.md, referencing which classes from the module are used by data, domain, and ui layers
  • [ ] Create app/src/androidTest/java/com/kunminx/puremusic/ArchitectureIntegrationTest.java to test integration between app module and architecture module (e.g., Repository pattern, ViewModel base classes)
  • [ ] Add a CONTRIBUTING.md section explaining when changes require updates to both architecture and app modules
  • [ ] Create a dependency diagram in the README showing how architecture → data → domain → ui layers depend on the :architecture module

🌿Good first issues

  • Add unit tests for DataRepository.java methods (getCurrentUser, getAlbumInfo) to improve test coverage; currently only has ExampleInstrumentedTest.java stub. Start by mocking AccountService and verifying LiveData emissions.
  • Implement a local caching layer (SharedPreferences or Room) in DataRepository for album metadata and user preferences to reduce API calls; current implementation has no persistence. Add fallback logic when network is unavailable.
  • Extend PlayerReceiver.java to emit more granular playback events (buffering %, seek position) and expose them via SharedViewModel LiveData so UI fragments can bind detailed player states; currently only broadcasts play/pause state.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 543eb86 — update lib (KunMinX)
  • e4cc9e6 — update diff logic (KunMinX)
  • d175a49 — update state diff (KunMinX)
  • a51343a — update lib (KunMinX)
  • 45cdc32 — update player lib and relative logic (KunMinX)
  • 833e3b6 — update lib (KunMinX)
  • 20d9682 — Merge branch 'master' of https://github.com/KunMinX/Jetpack-MVVM-Best-Practice (KunMinX)
  • a507af3 — update lib (KunMinX)
  • 95399d1 — Bump androidx.recyclerview:recyclerview from 1.3.0 to 1.3.1 (#215) (dependabot[bot])
  • 4902551 — update lib ver (KunMinX)

🔒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.

Concerning signals · KunMinX/Jetpack-MVVM-Best-Practice — RepoPilot