A deliberately minimal Kotlin Multiplatform + Compose Multiplatform app (Android, iOS, Desktop).
The app is just a stage for build-time configuration: a coffee-ordering screen with a debug
strip at the bottom that prints every config value on screen. Your job in the workshop is to
replace the hardcoded AppConfig with a generated one from BuildKonfig and watch those values
change per build flavor.
The screen reads all config (top-bar title, prices, debug strip) from a single
AppConfig object, so once it's BuildKonfig-generated the UI updates with no UI code changes.
Note: the Gradle project is still named
ConfigurationManagement(unchanged on purpose). The visible app title comes fromAppConfig.APP_NAMEand shows "CoffeeMate (DEV)".
- JDK 17+ (required for the Desktop target / packaging; JDK 11 is the language level).
- Android Studio or IntelliJ IDEA with the Kotlin Multiplatform plugin.
- Xcode 15+ (only for the iOS target).
- Kotlin 2.4.0 · Compose Multiplatform 1.11.1 · AGP 9.2.1 — all pinned in
gradle/libs.versions.toml.
Desktop is the fastest feedback loop — use it during the workshop:
# Desktop (JVM) — recommended
./gradlew :desktopApp:run
# Desktop with hot reload
./gradlew :desktopApp:hotRun --auto
# Android (install on a running emulator/device)
./gradlew :androidApp:assembleDebug
./gradlew :androidApp:installDebug
# iOS — open the Xcode project and Run
open iosApp/iosApp.xcodeprojThe shared UI/logic lives in :shared and is consumed by every platform entry point, so a change
in shared shows up on all targets.
File (shared/src/...) |
Purpose |
|---|---|
commonMain/.../AppConfig.kt |
The hardcoded config — the thing you replace with BuildKonfig. |
commonMain/.../CoffeeMenu.kt |
Coffee menu data + formatPrice() (reads AppConfig.CURRENCY). |
commonMain/.../PaymentProvider.kt |
Interface + expect getPaymentProvider(). |
<target>Main/.../PaymentProvider.<target>.kt |
Per-platform actual (currently all "GenericPay"). |
commonMain/.../App.kt |
The Compose screen + the DebugStrip that prints all config. |
Every spot you need to touch is marked with a // TODO(workshop): comment. Do them in order:
-
Apply the BuildKonfig plugin. In
shared/build.gradle.kts, addalias(libs.plugins.buildkonfig)to theplugins { }block. The version is already pinned ingradle/libs.versions.toml(buildkonfig = "0.21.2", plugin idcom.codingfeline.buildkonfig) — do not change versions. -
Configure the
buildkonfig { }block inshared/build.gradle.kts:packageName = "com.mw.mdp.configmanagement"- In
defaultConfigs { }, declare the five fields currently inAppConfig:ENVIRONMENT,API_URL,APP_NAME(String),CURRENCY(String),ANALYTICS_ENABLED(Boolean). UsebuildConfigField(STRING, "API_URL", "http://localhost:8080"), etc. - Give the object the same name with
objectName = "AppConfig".
-
Delete
commonMain/.../AppConfig.ktand rebuild. The generatedAppConfigtakes its place; the UI keeps compiling because the field names match. -
Add build flavors (e.g.
devvsprod). UsetargetConfigs/flavor overrides soprodgetsENVIRONMENT = "prod", a realAPI_URL,APP_NAME = "CoffeeMate",CURRENCY = "EUR",ANALYTICS_ENABLED = true. Switch flavor via thebuildkonfig.flavorGradle property and watch the debug strip and prices change on screen. -
Source-set override (
PaymentProvider). Replace the platformactual getPaymentProvider()implementations so each region returns its own provider name (e.g.GooglePayon Android,ApplePayon iOS). The "Payment provider" line in the debug strip updates per target — no common code changes.
Re-run ./gradlew :desktopApp:run after each step to see the effect immediately.