Gainful: A Three-Platform Financial Tracker Built with Kotlin Multiplatform and Compose
Kotlin Multiplatform has matured enough that a solo developer can ship a polished, data-driven app to three platforms without maintaining separate UI codebases. The concrete pain points documented here — datetime handling, iOS framework config, module namespacing — are exactly what teams evaluating KMP need to budget for.
Gainful is a personal financial tracking app that runs on Android, iOS, and desktop from a single Kotlin codebase. It uses Compose Multiplatform for a declarative UI layer, Room for local storage, Ktor for networking, and Koin for dependency injection, all structured under Clean Architecture with an MVI data flow.
The app covers dashboard metrics, holdings visualization with treemap charts, transaction filtering, and CSV import/export. The developer, Yoke, walks through the real-world friction points: replacing java.time with kotlinx-datetime in common code, configuring iOS static frameworks, and avoiding resource ID collisions across Gradle modules.
Development time was compressed by sharing nearly all code across platforms, with desktop hot reload speeding up UI iteration. The project is available on GitHub as a reference for anyone evaluating KMP for production cross-platform work.
The project's real value is as a reference architecture: it validates that Google's Now in Android patterns (Clean Architecture, MVI) port cleanly to KMP without major adaptation.
Room's KMP support is surprisingly seamless — the API surface is nearly identical to Android-native Room, which lowers the migration barrier for existing Android teams.
The three documented pitfalls (datetime, iOS static frameworks, resource IDs) are not KMP bugs but platform impedance mismatches that every cross-platform framework hits in some form.
Desktop as a development target doubles as a fast iteration loop; the hot reload capability makes it the de facto development environment even when the primary target is mobile.