All the client apps in this repository share the same version and are always released together. The version is derived entirely from git tags via the axion-release Gradle plugin — the git tags are the single source of truth, there is no version hardcoded in any file.
A release is started locally with a Gradle command. This creates an annotated git tag (major.minor.patch) and
pushes it to origin. Pushing the tag triggers the GitHub Actions that build and publish the individual clients.
./gradlew release # bump patch (default), e.g. 3.1.1 -> 3.1.2
./gradlew release -Prelease.versionIncrementer=incrementMinor # 3.1.1 -> 3.2.0
./gradlew release -Prelease.versionIncrementer=incrementMajor # 3.1.1 -> 4.0.0
./gradlew release -Prelease.version=4.0.0 # release an explicit versionTo see which version the current working tree would produce without releasing, run ./gradlew currentVersion.
On each push to the main branch a lava (staging/dev) build is triggered.
- android will be published to the
internaltrack of thelava kellner.teamapp on Google Play. - iOS will be published to TestFlight of the kellner.team Lava app.
For access to the development builds of Android and iOS contact development@kellner.team.
Every client is versioned from git tags via axion-release. Tags are plain semver with no v prefix
(e.g. 3.1.1). Two values are produced for the app stores:
versionName(the human-readable version shown to users):- Release build (working tree sits exactly on a clean tag):
major.minor.patch(e.g.3.1.1) - Lava (staging/dev) build, i.e. any build not on a release tag:
major.minor.patch-lava-commitHash(e.g.3.1.2-lava-a1b2c3d) — the short commit hash makes the build traceable to an exact commit (e.g. in Sentry).
- Release build (working tree sits exactly on a clean tag):
versionCode(the monotonic integer required by Google Play): a fixed baseline plus the total commit count (git rev-list --count HEAD). The baseline (in the rootbuild.gradle.kts) lifts every generated code above the highestversionCodethat was already published before versioning moved to git; the commit count keeps it strictly increasing onmainfrom there.
Run
./gradlew currentVersionto get theversionNameof the current working tree.
The versionCode is deterministic — the same commit always produces the same number, with no external build state.
The trade-off is that two builds from the same commit share a versionCode, and the stores reject a duplicate. This
only matters when you need to re-upload from the same commit (e.g. a CI re-run after the artifact was already
published, or a re-publish without a source change). When that happens, bump the counter with an empty commit:
git commit --allow-empty -m "rebump versionCode"iOS uses the very same axion-release version: the set-version.sh build phase asks
Gradle (./gradlew currentVersion) and patches the built Info.plist, so nothing is hardcoded in the repo and the
working tree stays clean (the values in Targets/*/…plist are placeholders and are never used).
Two things differ from Android:
- Apple only accepts numbers in
CFBundleShortVersionString, so the lava suffix cannot live there. It goes into theVERSION_SUFFIXkey instead, andGlobals.swiftre-joins the two into the same version name Android reports (e.g.3.1.2-lava-a1b2c3d). CFBundleVersionis not the commit count. It uses Xcode Cloud's auto incrementing build number ($CI_BUILD_NUMBER), which sidesteps the duplicate-upload problem described above. Local builds get1.
Xcode Cloud clones shallow and without tags, so ci_post_clone.sh restores the full history before the build.
To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:assembleDebug
- on Windows
.\gradlew.bat :composeApp:assembleDebug
To also install and launch it in a running emulator use the :composeApp:installDebug task.
To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.
open iosApp/iosApp.xcodeprojThis is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).
-
/composeApp is for code that will be shared across the Compose Multiplatform applications ( android & desktop). It contains several subfolders:
- commonMain is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location for the Android specific part have a look at androidMain.
-
/iosApp contains the iOS application. It's the entrypoint for the iOS app. This is also where the UI (SwiftUI) for iOS lives.
-
/shared is for the code that will be shared between all targets in the project. The most important subfolder is commonMain. If preferred, you can add code to the platform-specific folders here too.
Learn more about Kotlin Multiplatform…

