How it started#
Earlier this year my brothers and I decided to loose some weight in a challenge with each other. We searched for an app that would let us set fitness challenges, log our weight, steps and distances, and see who was winning.
That hunt did not last long. Every app we tried was either full of ads or so annoying to use that none of us kept opening it. The ones that were ad-free wanted a subscription for the most basic features. We ended up tracking in an app, but not all of us could use it because it was buggy.
So I decided to build the thing myself. I had been wanting to try Kotlin Multiplatform for a while, and this was the perfect excuse. droprace was born.
A look at the app#


The Tech Stack#
The whole app is built on a small set of technologies, and they all pull their weight. From the app itself to the design behind it:


One codebase, two platforms#
The core lives in a shared/ Kotlin Multiplatform module. All the business logic, the repositories and the entire UI are written once and compiled for Android and iOS. The Android app is just a thin shell with a MainActivity, and iOS wraps the same Compose UI in a thin SwiftUI host. When I change something on the home screen, both platforms get the change. No feature drift between platforms, and very little duplicate work.
Navigation is handled with type-safe Jetpack Navigation Compose, which gives routing that the compiler checks instead of free-string routes. Dependency injection comes from Koin, which is small, Kotlin-first and has first-class support on both platforms.
Supabase for everything backend#
I did not want to run a server for a hobby project, so Supabase carries the whole backend. PostgreSQL stores the data, Auth handles Google sign-in. Storage is used for avatars and other media.
The database schema is not a black box either. It lives as SQL migrations in the supabase/ folder of the repo, from the initial tables to the Row Level Security policies that make sure users can only see their own challenges. The leaderboard itself is a Postgres function, get_leaderboard(), which the app calls instead of re-implementing the ranking logic on the client.
Builds and releases that run by themselves#
CI/CD runs on GitHub Actions. Every release build builds the Android app and the iOS framework. A dedicated release workflow writes the changelog from Conventional Commits, creates a git tag and uploads the build to the Play Store’s internal track. I can trigger a release to the internal track in the Play Store with one click, which is a surprisingly nice feeling.
One detail I like: BuildKonfig switches between the local Supabase instance and production based on a flag in local.properties. Local development talks to a local Supabase via Podman, CI and other developers automatically build against production. That keeps my local experiments away from real user data.
Where to find it#
If you want to take a look, head over to droprace.app. The app is currently available in open testing, so if you want to run a challenge with friends you can jump in and give feedback. The full release in the stores is not far away, and I am looking forward to seeing people actually use the thing.
