A tiny, deliberately silly Material 3 bubble-wrap simulator — and a complete consumer example for compose-desktop-native's bridge plugin: one shared Compose Multiplatform codebase that runs
- on Android (Google's androidx.compose artifacts),
- on upstream Compose Desktop (JVM), and
- as self-contained native executables — Windows (mingwX64), Linux (x64 / arm64) and macOS (arm64) — no JVM, rendering through SDL3/Skia.
Pop bubbles, resize them with a slider, flip dark/light, get a fresh sheet.
Bouncy spring physics included. The bubble logo and the strings come from
composeResources/ — same files, same generated Res.* accessors on
every platform; on the native targets the bridge bundles them into
data.kres next to the executable automatically.
Two modules, the current Android-recommended shape for a KMP app:
:shared— the whole app as a Compose Multiplatform library. The Android target comes from AGP's KMP library plugin (com.android.kotlin.multiplatform.library), declared asandroidLibrary {}right inside thekotlin {}block — noandroid {}block, no manifest. The jvm/native desktop entry points and the bridge plugin live here too.:androidApp— a plaincom.android.applicationmodule: one Activity, one manifest,implementation(project(":shared")).
shared/src/commonMain contains only plain Compose code against the
official androidx.compose.* API, and shared/build.gradle.kts declares
only the official org.jetbrains.compose.* coordinates. One line in
settings.gradle.kts does the rest:
plugins { id("com.bitsycore.compose-desktop-native.bridge") version "0.1.17" }On the Kotlin/Native desktop targets the plugin substitutes
compose-desktop-native's klibs for those coordinates; every other target keeps
resolving upstream — on Android, JetBrains' own artifacts already redirect
to Google's androidx.compose.*, the exact same trick one layer up. SDL3 and
its codecs ship as static archives inside the klibs — nothing to install,
and a distributable is just the executable.
The artifacts live on GitHub Packages, which requires authentication even for
public downloads. Put a classic PAT with read:packages in
~/.gradle/gradle.properties:
gpr.user=<your-github-username>
gpr.token=<your-pat># Native — self-contained executable, no JVM
./gradlew :shared:runDebugExecutableMingwX64 # Windows
./gradlew :shared:runDebugExecutableLinuxX64 # Linux
./gradlew :shared:runDebugExecutableMacosArm64 # macOS (Apple Silicon)
# Upstream Compose Desktop (JVM) — the same App()
./gradlew :shared:run
# Android — the same App() on androidx.compose
./gradlew :androidApp:installDebug # or open in Android StudioLinux needs the usual GL/X11/fontconfig dev basics at link time
(libfontconfig-dev libgl1-mesa-dev libx11-dev).
Publish snapshots from a compose-desktop-native checkout
(publishMingwX64PublicationToMavenLocal publishKotlinMultiplatformPublicationToMavenLocal :compose-desktop-native-bridge:publishToMavenLocal), then:
./gradlew :shared:runDebugExecutableMingwX64 \
-PbridgeVersion=0.0.0-SNAPSHOT \
-PcomposeDesktopNativeVersion=0.0.0-SNAPSHOT