Skip to content

cmp-share

cmp-share

Target support: see TARGET_MATRIX.md — the single source of truth for which KMP targets every module ships and why.

Cross-platform share-sheet library for Kotlin Multiplatform.

Stable API. @ExperimentalShareApi is retained as a deprecated no-op so existing @OptIn(...) call sites keep compiling; both it and the -opt-in compiler flag are now redundant and can be deleted. Ships alongside the other cmp-* modules at the shared kmptoolkit.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 ShareError hierarchy + ShareResult for 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.

Android — direct-to-app share (skip the chooser)

Set ShareOptions.targetPackage to route the payload straight to a specific app (WhatsApp, Instagram, …) instead of the system chooser:

Share.file(
    uri = "content://…/status.mp4",
    mimeType = "video/mp4",
    options = ShareOptions(targetPackage = "com.whatsapp"),
)
  • If the target package isn't installed, can't handle the payload, or isn't visible under Android 11+ package-visibility, it falls back to the normal chooser (never fails).
  • targetPackage is Android-only — ignored on iOS / desktop / web (those platforms have no per-app targeting).
  • Android 11+ package visibility is handled for you. cmp-share's own manifest declares the <queries> SEND intent, merged into every consumer via manifest-merger — so a target app is visible without any consumer-side <queries> boilerplate. (Same zero-config contract as the bundled ShareInitProvider + FileProvider.)

iOS — present via key-window

Default: cmp-share traverses UIApplication.keyWindow.rootViewController.topMostController to find the presenting UIViewController. To anchor to a specific VC:

Share.text(
    content = "hi",
    options = ShareOptions(presentingController = myUIViewController),
)

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

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 stable

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 4 2026-09-13 full —
iosMain ✅ ✅ real 0 2 2026-09-13 full —
macosMain ✅ ✅ real 0 2 2026-09-13 full —
jvmMain ✅ ✅ real 0 2 2026-09-13 full —
jsMain ✅ ✅ real 0 2 2026-09-13 full —
wasmJsMain ✅ ✅ real 0 2 2026-09-13 full —
mingwMain 🟡 🟡 partial 2 2 2026-09-13 partial —
linuxMain ✅ ✅ real 2 2 2026-09-13 full —
tvosMain ✅ ✅ real 0 3 2026-09-13 (legacy:full) —
watchosMain 🟡 🟡 partial 4 2 2026-09-13 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.