cmp-share¶
cmp-share¶
Cross-platform share-sheet library for Kotlin Multiplatform.
Status: Experimental. All public APIs marked
@ExperimentalShareApi. Ships alongside the othercmp-*modules at the sharedkmptoolkit.version.
Features¶
- Payload variants: text, URL, image bytes, file URI, multi (composite)
- Platforms: Android, iOS, macOS, JVM Desktop, JS, wasmJs
- Native UI: each platform uses its OS-native share surface — no custom Compose UI
- Suspend API: integrates with structured concurrency; cancellation propagates correctly
- Typed errors: sealed
ShareErrorhierarchy +ShareResultfor completion / cancel / failure - Zero third-party deps: native platform APIs only (kotlinx-coroutines + kotlinx-browser already in kmp-toolkit)
Platform support¶
| Platform | Backing API | Notes |
|---|---|---|
| Android | Intent.ACTION_SEND + Intent.createChooser |
Files via FileProvider (auto-wired); ACTION_SEND_MULTIPLE for Multi payloads |
| iOS | UIActivityViewController |
Anchored to key-window root; iOS 14+ |
| macOS | NSSharingServicePicker |
Anchored to key-window contentView; macOS 11+ |
| JVM (Desktop) | System clipboard (text/url/image) + AWT FileDialog SAVE (file) |
Best-effort — NOT a native share sheet |
| JS / wasmJs | navigator.share if available; else navigator.clipboard.writeText fallback |
Must invoke from a user-gesture handler (browser security) |
Not targeted: tvOS, watchOS, Linux native, mingwX64, wasmWasi. Per cmp-toolkit Tier-3 exclusion policy. Adding them later requires upstream share-API coverage.
Install¶
// build.gradle.kts (your consumer app)
dependencies {
val kmptoolkit = "3.2.13" // or latest — see https://central.sonatype.com/artifact/io.github.mobilebytelabs/cmp-share
implementation("io.github.mobilebytelabs:cmp-share:$kmptoolkit")
}
Quick start¶
Share text¶
@OptIn(ExperimentalShareApi::class)
@Composable
fun ShareButton() {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
val result = Share.text(
content = "Hello from cmp-share!",
options = ShareOptions(chooserTitle = "Share quote"),
)
when (result) {
ShareResult.Completed -> showToast("Shared")
ShareResult.Cancelled -> { /* user dismissed */ }
is ShareResult.Failed -> showToast("Share failed: ${result.cause}")
}
}
}) { Text("Share") }
}
Share a generated file¶
@OptIn(ExperimentalShareApi::class)
suspend fun shareReceipt(pdfUri: String) {
Share.file(
uri = pdfUri,
mimeType = "application/pdf",
filename = "receipt.pdf",
options = ShareOptions(chooserTitle = "Send receipt"),
)
}
Share an image¶
@OptIn(ExperimentalShareApi::class)
suspend fun shareSnapshot(pngBytes: ByteArray) {
Share.image(bytes = pngBytes, mimeType = "image/png", filename = "snap.png")
}
Multi-payload (text + file + image)¶
@OptIn(ExperimentalShareApi::class)
suspend fun shareReport(summary: String, pdfUri: String, chartPng: ByteArray) {
Share.multi(
payloads = listOf(
SharePayload.Text(summary),
SharePayload.File(pdfUri, "application/pdf", "report.pdf"),
SharePayload.Image(chartPng, "image/png", "chart.png"),
),
)
}
Per-platform setup notes¶
Android — zero-config¶
The library declares its ShareInitProvider + FileProvider (authority
${applicationId}.cmp-share.fileprovider) via manifest-merger. Consumer apps don't
need to declare anything.
iOS — present via key-window¶
Default: cmp-share traverses UIApplication.keyWindow.rootViewController.topMostController
to find the presenting UIViewController. To anchor to a specific VC:
JVM Desktop — best-effort, NOT a native share sheet¶
- Text / URL → system clipboard write + log "copied to clipboard"
- Image → clipboard image transferable
- File → AWT
FileDialog(SAVE)save-as prompt - Multi → first text-payload subset → clipboard, first file-payload → FileDialog
Documented loudly as "best-effort"; consumer apps that need a true native macOS share sheet on Desktop should run via macOS Compose-MP target.
JS / wasmJs — user-gesture required¶
// ✅ CORRECT — Share.share() inside Composable onClick (user-gesture activation)
Button(onClick = { scope.launch { Share.text("hi") } }) { ... }
// ❌ WRONG — LaunchedEffect is NOT a user-gesture call stack
// Browser blocks navigator.share() / navigator.clipboard.writeText()
LaunchedEffect(Unit) { Share.text("hi") } // returns ShareResult.Failed(UserGestureMissing)
See also¶
- SPEC: idea-layer/modules/cmp-share/SPEC.md
- API: idea-layer/modules/cmp-share/API.md
- ADRs: idea-layer/modules/cmp-share/adrs/
- Sibling modules: cmp-intent-launcher, cmp-app-intents, cmp-open-url, cmp-deep-link
Module reference¶
Module Identity (auto-gen)
| Artifact | Package | Current version | Maven | Since | API tier |
|---|---|---|---|---|---|
io.github.mobilebytelabs:cmp-share |
com.mobilebytelabs.kmptoolkit.share |
UNKNOWN |
Central | 2026-05-30 | experimental |
Module purpose (one paragraph):
§2 Per-Platform Parity Matrix (auto-gen)¶
| Target | Source-set present | Real impl | UnsupportedPlatform stub | .kt count | Last reviewed | Coverage | Notes |
|---|---|---|---|---|---|---|---|
| androidMain | ✅ | ✅ real | 0 | 3 | 2026-06-01 | full | — |
| iosMain | ✅ | ✅ real | 0 | 1 | 2026-06-01 | full | — |
| macosMain | ✅ | ✅ real | 0 | 1 | 2026-06-01 | full | — |
| jvmMain | ✅ | ✅ real | 0 | 1 | 2026-06-01 | full | — |
| jsMain | ✅ | ✅ real | 0 | 1 | 2026-06-01 | full | — |
| wasmJsMain | ✅ | ✅ real | 0 | 1 | 2026-06-01 | full | — |
| mingwMain | 🟡 | 🟡 partial | 3 | 1 | 2026-06-01 | partial | — |
| linuxMain | ✅ | ✅ real | 2 | 1 | 2026-06-01 | full | — |
| tvosMain | 🟡 | 🟡 wontfix-OS | 2 | 1 | 2026-06-01 | wontfix-OS | — |
| watchosMain | 🟡 | 🟡 partial | 4 | 1 | 2026-06-01 | partial | — |
Legend (Real impl): ✅ real impl, 🟡 partial / wontfix-OS / wontfix-infra / legacy stub, ⛔ not declared, — N/A.
Legend (Coverage enum, since 2026-06-01): full (all public-API methods backed by OS primitive) · partial (most real; some typed UnsupportedPlatform fallbacks for contracts that don't apply) · wontfix-OS (OS lacks the primitive) · wontfix-infra (impl possible but CI/toolchain blocks it) · (legacy:full|stub) (auto-derived; pre-opt-in modules — add a // LD-2-coverage: {enum} comment to the platform's primary .kt file to graduate). See RULE-LIB-DEVELOPMENT-MD-001 LD-2 + ADRs for accepted wontfix cases.
API reference¶
Each release ships the module's full Dokka HTML site inside its
-javadoc.jar artifact on Maven Central.
In IntelliJ / Android Studio the IDE mounts the jar and surfaces it automatically in hover popups, Quick Documentation, and Symbol search.