Search by tag: cmp

3 articles

Training Diary 2.0.0

Training Diary is out on the Google Play Store. It is a straightforward log for the training you actually do: sessions, exercises, and sets counted in reps or timed, with weight or elastic-band resistance. It works entirely offline — no account, no sign-in, no backend.

Session detail screen

The version number starts at 2.0.0 because this is not a first attempt. The original was a single-platform Android app I wrote years ago, before my official training; 2.0.0 is a complete rewrite in Kotlin Multiplatform, sharing one codebase across Android, iOS and Desktop. Android ships first.

Under the hood

The app is built with Compose Multiplatform on Kotlin 2.4, and all of the UI is shared — there is no per-platform screen code. The project is split into core:* and feature:* Gradle modules with a strict dependency direction: domain is pure Kotlin, data implements the interfaces domain declares, and features never depend on each other. Screens follow MVI, wiring is Koin, and persistence is SQLDelight over local SQLite — typed queries, no reflection, and the same schema on every target. Navigation uses Navigation 3.

The rest timer was the most interesting cross-platform problem. A countdown that only ticks while the app is in the foreground is useless between two sets, so the alarm is scheduled with the platform, not with a coroutine: an expect/actual scheduler backed by AlarmManager on Android and by a local notification on iOS. It fires when the rest is over whether you switched apps or locked the screen — and it degrades gracefully to an inexact alarm if the exact-alarm permission is declined, rather than crashing.

Everything was written test-first, guided by ZOMBIES: roughly four in ten Kotlin files in the repository are tests, covering domain logic, ViewModels, SQLDelight queries, the Koin graph, navigation, and the Compose UI itself through runComposeUiTest. detekt, with the Compose ruleset, gates every build.

Accessibility

Accessibility was part of the tests, not a pass at the end: TalkBack support, touch targets and layouts that survive the largest text and display sizes, light and dark themes, English and French, and tablet and landscape layouts that are laid out rather than stretched.

Your sessions stay on your device. You can export them to a file whenever you want and restore them just as easily.

Timer

Water Tracker 1.1.0

Version 1.1.0 introduces updates focused on tracking accuracy and interface adjustments.

New Features

  • Last Fill Indicator: Displays the timestamp of the most recent water entry to prevent duplicate logging during active sessions.
  • Automatic Day Transition: The active tracking day updates automatically upon app resumption if the system clock has passed midnight, regardless of whether the app was in the foreground or background.
  • Current Day Navigation: A dedicated button allows immediate return to the current date view from historical logs.

Accessibility

  • Zoom Support: Improved compatibility with system-level zoom and larger text settings. Layout adaptation is functional across standard magnification levels, though some elements may not fully render at maximum zoom settings.

This update is available now on Google Play.

Water Tracker main screen in landscape mode

Water Tracker

Here is the release of a small hydration tracker, made with Compose Multiplatform (CMP). It is a lightweight, high-performance utility app for Android and iOS.

The goal was to create a seamless, reactive experience while keeping the UI layer entirely decoupled from business logic: the "Dumb UI" pattern, or more professional sounding: Unidirectional Data Flow (UDF).

To ensure the UI remains a pure projection of the state, I moved all logical operations—including orientation-based grid calculations (rows/cols), date formatting, and accessibility strings—into the ViewModel. Even the layout math, like chunking the list of 8 glasses into rows, is handled in the ViewModel. This makes the Composables "dumb" and easy to preview, while the entire user experience becomes unit-testable without an emulator.

The app uses Room for local persistence, leveraging its new Kotlin Multiplatform capabilities. A database factory handles path differences between Android’s internal storage and iOS’s document directory through the expect/actual pattern. Koin ensures dependency injection.

To match the new requirement of Android SDK 36+, the UI is adaptive to ensure a pleasant experience both in portrait and lanscape modes. The glass shape is drawn using the Canvas API, and I played with a few simple animations to smooth the state changes.

I also experimented with custom semantic roles and state descriptions, to fully support screen readers like TalkBack and VoiceOver.

Water Tracker main screen in landscape mode