ExerSight is an Android app that uses the device camera and on-device pose detection to count workout repetitions in real time. Point the camera at yourself while doing push-ups, squats, or jumping jacks and the app tracks your form and counts reps as you go.
- Real-time pose detection via Google ML Kit, fed from a CameraX analysis pipeline.
- Repetition counting for push-ups, squats, and jumping jacks, each with its
own detection logic (
data/exercise). - Front/back camera flip while counting.
- Runtime camera permission handling with a graceful "permission unavailable" fallback.
| Layer | Choice |
|---|---|
| UI | Jetpack Compose, Material 3 |
| Navigation | Navigation 3 (androidx.navigation3) with type-safe destinations |
| DI | Hilt, with @AssistedInject/@AssistedFactory for passing navigation arguments into ViewModels |
| Camera | CameraX (camera-core, camera-view, camera-video, camera-mlkit-vision) |
| Pose detection | Google ML Kit Pose Detection |
| Async | Kotlin Coroutines & Flow |
| Build | Gradle version catalog (gradle/libs.versions.toml), KSP (no kapt) |
The app uses Navigation 3, Google's Compose-first navigation library that
replaces NavController/NavHost with a back stack you own directly:
- Destinations are plain
NavKeyimplementations (seeui/navigation/NavKey.kt) —HomeandExerciseCounter, the latter carrying its arguments (exerciseType,exerciseTitle) as typed properties instead of a string route. - The back stack is a
NavBackStackcreated withrememberNavBackStackinExerSightApp.ktand rendered withNavDisplayinui/navigation/AppNavigation.kt, using theentryProvider { entry<T> { } }DSL. ExerciseCounterViewModelreceives its destination directly through Hilt's assisted injection (@AssistedInject+@AssistedFactory), so there's noSavedStateHandlestring-key lookup — the navigation argument is just a constructor parameter.
app/src/main/java/yao/ic/mediapipe/
├── data/exercise/ # Per-exercise rep-counting logic (PushUp, Squat, JumpingJack)
├── di/ # Hilt modules (e.g. PoseDetector provider)
├── domain/ # Pose/exercise state and the CameraX ImageAnalysis.Analyzer
├── ui/
│ ├── navigation/ # NavKey destinations + Navigation 3 setup
│ ├── screen/home/ # Exercise picker screen
│ ├── screen/exercise_counter/ # Camera screen + rep counter overlay + ViewModel
│ ├── common/components/ # Shared composables (camera preview, pose overlay, dialogs)
│ └── theme/ # Compose theme
└── utils/ # Geometry/scaling helpers for overlay drawing
- Android Studio with support for AGP 9.x / Gradle 9.x.
- JDK 17.
- A device or emulator running Android 8.0 (API 26) or later, with a camera.
./gradlew :app:assembleDebugDependency versions are centralized in gradle/libs.versions.toml — bump a
version there rather than in app/build.gradle.kts.
The app requests CAMERA at runtime (via Accompanist Permissions) before
showing the exercise counter screen.