Skip to content

kmpbits/SwiftExportKMPDemo

Repository files navigation

Swift Export KMP Demo

Demo project for the KMP Bits article on Swift Export. Shows three things Swift Export in Kotlin 2.3.20 handles that the old Objective-C bridge didn't: exhaustive enum switches, typed sealed class properties, and variadic functions.

Android — Jetpack Compose
iOS — SwiftUI
Shared logic — Kotlin Multiplatform (shared module)


What's inside

Enum tab

A Kotlin enum class Status { Loading, Success, Error } exported as a real Swift enum. The SwiftUI switch is exhaustive with no default case. Add a new case in Kotlin and Swift breaks at compile time.

Sealed tab

A WeatherState sealed class with Loading, Success, and Error subclasses. Properties (temperature, condition, message) arrive typed and named correctly on the Swift side — no adapter, no manual unwrapping. Sealed classes export as an open class hierarchy, so the switch isn't exhaustive, but the data is clean.

Vararg tab

A shared Logger object with a vararg messages: String parameter. Swift Export maps it to native variadic syntax: Logger.shared.log(tag: "Demo", messages: "a", "b", "c") instead of an array literal.


Project structure

shared/
  commonMain/
    Status.kt          — enum class
    WeatherState.kt    — sealed class
    WeatherViewModel.kt — plain class, no framework dependency
    Logger.kt          — object with vararg fun

composeApp/
  androidMain/
    App.kt                     — Compose UI (Enum, Sealed, Vararg screens)
    WeatherAndroidViewModel.kt — ViewModel wrapper for Android

iosApp/
  iosApp/
    ContentView.swift  — TabView, EnumDemoView, LoggerDemoView
    WeatherView.swift  — WeatherViewModelWrapper + WeatherView

WeatherViewModel in shared is a plain class with no ViewModel inheritance. Swift Export exports transitive dependencies — if you add lifecycle-viewmodel to commonMain, it tries to export the entire lifecycle library and fails on generic APIs it can't handle. The ViewModel wrapper lives in composeApp (Android) and in Swift (iOS) instead.


Requirements

  • Android Studio Meerkat or later
  • Xcode 16+
  • Kotlin 2.3.20
  • JDK 17 (Android Studio's bundled JDK — see iOS note below)

Running

Android

Open in Android Studio and run composeApp on a device or emulator.

Or from the terminal (use Android Studio's JDK to avoid AGP/JDK incompatibilities):

JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
  ./gradlew :composeApp:assembleDebug

iOS

Open iosApp/iosApp.xcodeproj in Xcode and run on a simulator or device.

The Xcode build phase script calls embedSwiftExportForXcode and pins JAVA_HOME to Android Studio's bundled JDK. This is required — Xcode uses the system Java by default, and JDK 17+ versions that AGP doesn't support will cause the Gradle task to fail silently, producing a "No such module 'Shared'" error in Swift compilation.

The build phase script:

if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
  echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
  exit 0
fi
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
cd "$SRCROOT/.."
./gradlew :shared:embedSwiftExportForXcode

Enabling Swift Export

Two changes from a standard KMP setup:

1. shared/build.gradle.kts — replace binaries.framework {} with the swiftExport {} block:

import org.jetbrains.kotlin.gradle.swiftexport.ExperimentalSwiftExportDsl

kotlin {
    iosArm64()
    iosSimulatorArm64()

    @OptIn(ExperimentalSwiftExportDsl::class)
    swiftExport {
        moduleName = "Shared"
        flattenPackage = "com.yourapp.shared"
    }
}

The @OptIn is required. Without it the block is silently ignored and the task never registers.

2. Xcode build phase — replace embedAndSignAppleFrameworkForXcode with embedSwiftExportForXcode (with the JAVA_HOME pin above).


Known limitations

  • Flows (StateFlow, SharedFlow) are not yet part of Swift Export's stable output. Use KMP-NativeCoroutines or SKIE for async types.
  • Sealed classes export as open class hierarchies in Swift — no exhaustive switch.
  • Enum cases keep Kotlin capitalization: .Loading, not .loading. If you're migrating from SKIE (which lowercases them), call sites will break.
  • Swift Export is still experimental. Check the official docs for current limitations.

Related

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors