Runique
Advanced running tracker syncing mobile and smartwatch.
The Overview
Runique connects a phone companion app to a Wear OS smartwatch for live run tracking. Tracking a run end-to-end means continuous stream sync between the watch and the host phone, real-time geospatial data, map rendering, and the Android Health Services APIs.
The Challenge
Fitness apps need precision. Doze mode kills background processes, network latency drops mid-run, and async streaming between a phone and a Wear OS watch routinely causes sync issues.
The goal was to keep a live GPS trace on Google Maps while capturing health biometrics from the watch, without draining the battery or stalling when the user hits a cellular dead-zone.
Architecture & Technical Decisions
Runique is split into App, Auth, Run, Analytics, and Wear modules under a Clean Architecture layout.
Custom Gradle convention plugins encapsulate features and keep build times down. The local Room database is the source of truth: biometrics from the Wear OS Health Services API land in local buffers first, then Ktor batches them to the backend when the network is up. GPS emissions are high-frequency, so the pipeline leans heavily on Kotlin Flow operators, with Turbine in the test modules to keep async state assertions stable in CI.
Shared code vs platform scope
Rather than duplicating logic, the core domain and data layers serve both phone and Wear targets. Each presentation module then injects its own design system through Koin: Material 3 for the phone, a smaller Wear OS system for the watch.
Impact & Results
- Sub-second sync between watch and phone for live health metrics.
- Continuous GPS recording through cellular drops and Doze-mode cycles.
- Faster builds via dynamic feature modularization and custom Gradle convention plugins.