SmartForm is an Android fitness app that performs on-device pose detection and hand tracking to help users train with better form, count reps, and control the experience hands-free with gestures. It is built fully on-device with CameraX, ML Kit, MediaPipe, and Jetpack Compose.
It currently supports guided tracking for curls, squats, and push-ups, with posture-aware rep counting, per-exercise calibration, rep-quality scoring, and a graph-driven session summary — all wrapped in a dark, athletic "neon" UI.
Grab the APK for your device from the latest release:
| Variant | Devices | Size |
|---|---|---|
| arm64-v8a | Recommended — all modern phones (2017+) | ~45 MB |
| armeabi-v7a | Older 32-bit devices | ~36 MB |
| universal | Works on any device | ~130 MB |
Requires Android 8.0 (API 26)+. Also available on Uptodown:
| Onboarding | Live workout HUD | Session summary | Session breakdown |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
SmartForm combines three capabilities in a single camera pipeline:
- Real-time body pose detection for exercise analysis
- Real-time hand landmark tracking for gesture input
- Live rep counting and form evaluation for supported exercises
The experience is designed around a hands-free workout loop:
- Grant camera access
- Choose an exercise mode (gesture or on-screen)
- Start a session (pinch gesture or on-screen button)
- Perform reps while SmartForm evaluates posture and rep quality
- End the session and review the graph-based summary
- Full-body pose detection using ML Kit Pose Detection
- Live skeleton overlay aligned to the camera preview
- Continuous, backpressure-limited frame processing tuned for on-device use
- 21-point hand landmark detection using MediaPipe Tasks
- Debounced, hold-based gestures to reduce accidental triggers
Pinch-hold— start / stop a session (or capture a calibration pose)Open-palm-hold— switch exercise mode while paused
- On-screen controls (Start/Pause, End, mode selector) fully mirror the gestures, so the app stays usable if hand tracking is unavailable
Bicep curls, squats, and push-ups — each with its own rep thresholds and independent calibration. Push-up mode shows an in-app hint to place the phone to your side (front-camera pose tracking of a plank is unreliable).
- Tracks rep phases and counts completed reps in real time
- Posture gating is phase-independent — it checks stability/visibility that holds across a rep (body in frame, level, torso not swinging), so counting is never paused mid-rep by the movement itself
- Low-quality movement doesn't get credited as a valid rep
- Direction-agnostic depth model (correct for curls, squats, and push-ups)
- Flags shallow reps and overly fast reps, with a per-rep score
- Maintains a recent rep timeline and a running average session score
- Per-exercise calibration adapts thresholds to the user
- Stored locally with DataStore; reset-to-defaults supported
A scrollable dashboard rendered with hand-drawn Compose-Canvas charts:
- Average-score ring + total reps hero
- Score-per-rep line/area chart
- Verdict donut (good / shallow / too-fast) with legend
- Depth % and tempo small-multiple sparklines
- Full rep-quality timeline
- First-run screen with a gesture how-to and on-device privacy note
- Built-in debug panel for inspecting thresholds, angles, posture, and calibration
SmartForm uses a state-driven Compose UI backed by a ViewModel:
SessionViewModelowns all in-session state (rep counting, posture, quality/session tallies, calibration) and exposes an immutableSessionUiState. It survives configuration changes and persists the selected mode viaSavedStateHandle. Camera frames are fed in by plain method calls; hand frames land in a thread-safeStateFlow(they arrive on a background analyzer thread).SessionTrackeris a pure, Android-free accumulator for per-session rep quality — unit-tested on the host JVM.RepCounteris a hysteresis + confirm-frame + EMA state machine with an injectable clock, so its transitions are deterministically testable.PoseMathholds the shared joint-angle geometry used by rep counting and posture evaluation.- Frame pipeline: CameraX drives two
ImageAnalysisuse-cases on separate single-thread executors —PoseProcessor(ML Kit, YUV) andHandProcessor(MediaPipe, RGBA) — withSTRATEGY_KEEP_ONLY_LATESTbackpressure. Model initialization fails gracefully (a missing/corrupt hand model disables gestures instead of crashing). - Charts are drawn by hand with Compose
Canvas(ui/charts/) — no external charting dependency, fully theme-aware.
- Language: Kotlin
- UI: Jetpack Compose (Material 3), committed dark "neon" theme
- Architecture: MVVM (
ViewModel+ immutable UI state) with pure, testable domain logic - Camera: CameraX
- Pose detection: ML Kit Pose Detection
- Hand tracking: MediaPipe Tasks Vision
- Local storage: DataStore Preferences
- Testing: JUnit (host-JVM unit tests)
app/src/main/java/com/app/smartform/
├── calibration/
│ ├── CalibrationModels.kt
│ └── CalibrationStore.kt
├── camera/
│ └── CameraPreview.kt
├── gesture/
│ └── GestureDetector.kt
├── hand/
│ ├── HandModels.kt
│ ├── HandOverlay.kt
│ ├── HandProcessor.kt
│ └── YuvToRgbConverter.kt
├── pose/
│ ├── PoseFrame.kt
│ ├── PoseMath.kt
│ ├── PoseProcessor.kt
│ ├── PostureEvaluator.kt
│ └── SkeletonOverlay.kt
├── reps/
│ ├── ExerciseMode.kt
│ ├── RepCounter.kt
│ ├── RepQuality.kt
│ └── RepThresholds.kt
├── session/
│ ├── SessionStats.kt
│ ├── SessionTracker.kt
│ └── SessionViewModel.kt
├── ui/
│ ├── SessionSummaryScreen.kt
│ ├── charts/Charts.kt
│ └── theme/ (Color.kt, Theme.kt, Type.kt)
└── MainActivity.kt
Domain logic is covered by host-JVM unit tests:
./gradlew :app:testDebugUnitTestRepCounterTest— down/up transitions, hysteresis, form-gating, reset, arm selectionRepQualityEvaluatorTest— depth %, verdicts, tempo thresholds, curl (inverted-threshold) depthSessionTrackerTest— verdict tallies, average score, timeline cap, snapshotPoseMathTest— joint-angle geometryCalibrationProfileTest— default thresholds
- Android Studio with a current Android SDK (platforms 35+)
- Java 17+
- An Android device with a working camera (an emulator is not reliable for pose/hand validation)
./gradlew :app:installDebugClean reinstall:
./gradlew :app:uninstallDebug
./gradlew :app:installDebugRelease signing reads credentials from a git-ignored keystore.properties at the project root:
storeFile=/path/to/your.keystore
storePassword=********
keyAlias=********
keyPassword=********Then:
./gradlew :app:assembleReleaseNotes:
- The
releasebuild type enables R8 minification and resource shrinking, with keep rules for ML Kit / MediaPipe (app/proguard-rules.pro). - If
keystore.propertiesis absent, release falls back to the debug key so local test builds still work. - The output is a universal APK bundling native libraries for all ABIs, so it is large (~130 MB). This is fine for Uptodown; ABI splits can shrink it if needed.
- Min SDK: 26
- Target SDK: 35
- Compile SDK: 35
- Version: 1.0
Required permission:
<uses-permission android:name="android.permission.CAMERA" />The MediaPipe hand-landmark asset lives at app/src/main/assets/hand_landmarker.task.
- Gesture accuracy is best when the user is clearly visible and centered
- Very close distances reduce hand-landmark stability
- Low light reduces both pose and hand detection quality
- Push-ups are unreliable from a front camera; place the phone to your side
- The release APK is not yet 16 KB page-size compliant (a Google Play requirement for updates targeting Android 15+); it is unaffected on current devices and for Uptodown distribution
- Camera preview doesn't start — confirm camera permission, test on a physical device
- Gestures not recognized — keep the hand in frame, improve lighting, hold the gesture steadily; on-screen buttons always work as a fallback
- Reps not counting — check the form banner, make sure the selected mode matches the movement, or run calibration
- Calibration feels off — reset to defaults and recapture clean top/bottom poses
- Richer coaching cues during active reps
- Session history and trends across workouts
- Side-camera support for push-ups
- 16 KB page-size compliance for Play distribution
- Additional exercise modes and exportable summaries
SmartForm is still evolving. Contributions that improve detection quality, exercise logic, UI clarity, performance, and documentation are welcome.



