Compose Multiplatform (KMP) Setup
Recommended for new projects
Compose Multiplatform is the recommended integration path for new Maverick AI applications. It lets you share UI and business logic across Android and iOS in a single Kotlin codebase.
Overview
A Compose Multiplatform project uses a single shared module (composeApp) that contains all SDK logic and UI. The SDK is consumed as a Maven dependency in commonMain, making the same code available to both Android and iOS targets.
| Component | Description |
|---|---|
| Shared module | composeApp/ - Kotlin Multiplatform module with commonMain, androidMain, and iosMain source sets |
| Android entry point | MainActivity in androidMain - calls Evs.init(context) and hosts the Compose UI |
| iOS entry point | MainViewController in iosMain - calls Evs.init() and returns a ComposeUIViewController |
Project Setup
1) Create the project
Use the Kotlin Multiplatform Wizard or start from the kmp-compose-sample.
2) Configure plugins
In your root build.gradle.kts:
plugins {
id("com.android.application") version "8.12.0" apply false
id("org.jetbrains.kotlin.multiplatform") version "2.2.20" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false
id("org.jetbrains.compose") version "1.6.11" apply false
}
3) Add the Maven repository
Add the GitHub Packages Maven repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/everysight-maverick-AI/mav-ai-android-maven")
credentials {
username = providers.gradleProperty("gpr.user").orNull
password = providers.gradleProperty("gpr.key").orNull
}
}
}
}
Note
You must create a GitHub personal access token with the read:packages scope.
Create credentials in ~/.gradle/gradle.properties:
4) Add the SDK dependency
In your shared module composeApp/build.gradle.kts:
kotlin {
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "ComposeApp"
}
}
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation("com.everysight.mav2:maverick-ai-sdk:0.1.0")
}
androidMain.dependencies {
implementation("androidx.activity:activity-compose:1.10.1")
}
}
}
android {
namespace = "com.example.myapp"
compileSdk = 36
defaultConfig {
minSdk = 30
targetSdk = 36
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
Note
Use commonMain.dependencies so the SDK is available to both Android and iOS targets from a single declaration.
SDK Initialization
Initialize the SDK and register one IM2ResourcesResolver per platform. Required on KMP (and only on KMP — see the Resources guide for the rules per project type). The same one-liner body works on both platforms.
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalResourceApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Evs.init(applicationContext)
Evs.resourcesService.setResourcesResolver(object : IM2ResourcesResolver {
override fun loadResource(path: String): ByteArray? = try {
runBlocking { Res.readBytes("files/$path") }
} catch (e: Exception) { null }
})
setContent {
App(
initSdk = { /* already initialized above */ },
stopSdk = { Evs.stop() }
)
}
}
override fun onDestroy() {
Evs.stop()
super.onDestroy()
}
}
@OptIn(ExperimentalResourceApi::class)
fun MainViewController(): UIViewController {
Evs.init()
Evs.resourcesService.setResourcesResolver(object : IM2ResourcesResolver {
override fun loadResource(path: String): ByteArray? = try {
runBlocking { Res.readBytes("files/$path") }
} catch (e: Exception) { null }
})
return ComposeUIViewController { App() }
}
Res is your app's generated <your-app-package>.generated.resources.Res. The files/ prefix is required.
Resources
Put files once in composeApp/src/commonMain/composeResources/... and they are shared between Android and iOS:
| Kind | Where |
|---|---|
API key (sdk.key / app.key) |
composeApp/src/commonMain/composeResources/files/sdk.key (one shared file, found via the resolver above). For different keys per app store, use androidMain/assets/sdk.key + iosMain/resources/sdk.key. |
Glasses content (M2Image, M2Font, AR .obj/.png) |
composeApp/src/commonMain/composeResources/files/<your-path> (e.g. composeResources/files/images/apple.png, composeResources/files/fonts/foo.evfn, composeResources/files/obj_files/earth/earth.obj) |
| Phone-UI assets | composeApp/src/commonMain/composeResources/{drawable,font,values}/..., accessed via painterResource(Res.drawable.foo) / stringResource(Res.string.foo) |
Full picture (including how :composeApp:syncComposeResourcesForIos and the resolver each fit in) is in the Resources guide.
iOS Build Configuration
In your Xcode project's Compile Kotlin Framework Run Script, append :composeApp:syncComposeResourcesForIos. The script in the kmp-compose-sample is:
cd "$SRCROOT/.."
unset EXECUTABLE_BLANK_INJECTION_DYLIB_PATH
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\""
else
SDK_VERSION_ARG=""
if [ -n "$MAV2_SDK_VERSION" ]; then
SDK_VERSION_ARG="-PsdkVersion=$MAV2_SDK_VERSION"
fi
./gradlew $SDK_VERSION_ARG \
:composeApp:embedAndSignAppleFrameworkForXcode \
:composeApp:syncComposeResourcesForIos
fi
The two block headers are optional but recommended:
unset EXECUTABLE_BLANK_INJECTION_DYLIB_PATHclears a Xcode 15+ env var that, when present, makes the Kotlin/Native linker emit a spurious dylib reference.OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED=YESlets you skip the gradle invocation from the IDE when iterating on Swift-only changes — Xcode will still link against whatever framework Kotlin already produced.
The MAV2_SDK_VERSION / SDK_VERSION_ARG lines are also optional for normal usage. If MAV2_SDK_VERSION is unset (the default for everyone outside the SDK release pipeline), SDK_VERSION_ARG stays empty and Gradle resolves the SDK version from gradle.properties:sdkVersion. The env var is only used by the SDK build_system to validate a candidate release before publishing.
If you use CocoaPods (kotlin("native.cocoapods")), the [CP] Copy Pods Resources build phase already handles syncComposeResourcesForIos — no manual addition needed.
Required on KMP iOS
Without :composeApp:syncComposeResourcesForIos (or the CocoaPods equivalent), Res.drawable.* and SDK Compose UI screens (Evs.showUI(...)) will throw MissingResourceException at runtime.
Gradle Properties
Add these to your gradle.properties for optimal KMP/iOS builds:
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
android.useAndroidX=true
compose.kotlin.native.manageCacheKind=false
kotlin.native.cacheKind=none
Building
# Android debug APK
./gradlew :composeApp:assembleDebug
# iOS framework (from terminal - for CI)
./gradlew :composeApp:compileKotlinIosArm64
For iOS development, build and run through Xcode - select the iOS target scheme and run on a device or simulator. The Gradle build phase in Xcode handles framework compilation and resource sync automatically.
See Also
- Android Setup - Native Android project setup
- iOS Setup - Native iOS project setup
- API Keys - Configure SDK authentication