Resources
Where to put your API key, your glasses content (M2Image, M2Font, AR meshes, sounds), and your phone-UI assets — and what build steps and resolver code each platform needs. Pure rules. Pick your project type and follow the recipe.
At-a-glance
| Your project | API key (sdk.key / app.key) |
Glasses content (M2Image, M2Font, AR .obj/.png) |
Phone-UI assets | Build script | Resolver |
|---|---|---|---|---|---|
| Native Android | app/src/main/assets/sdk.key |
app/src/main/assets/<your-path> |
app/src/main/res/... (R.drawable.foo) |
None | Don't add one |
| Native iOS (SPM) | App bundle root (Xcode → "Copy Bundle Resources") | App bundle root or folder reference (Xcode → "Copy Bundle Resources") | Assets.xcassets, UIImage(named:) |
One Xcode Run Script (rsync, see iOS section) | Don't add one |
| KMP Compose | composeApp/src/commonMain/composeResources/files/sdk.key |
composeApp/src/commonMain/composeResources/files/<your-path> |
composeApp/src/commonMain/composeResources/{drawable,font,values}/... (Res.drawable.foo) |
Append :composeApp:syncComposeResourcesForIos to the iOS Run Script (or use CocoaPods which handles it) |
Yes — register one resolver, see KMP section |
That's the whole rule set. The rest of this page is the per-platform recipe.
Native Android
Where to put files
| Kind | Location |
|---|---|
| API key | app/src/main/assets/sdk.key (or app.key) |
| Glasses content | app/src/main/assets/<your-path> (e.g. assets/files/images/apple.png) |
| Phone-UI assets | app/src/main/res/... accessed via R.drawable.foo / painterResource(R.drawable.foo) |
Initialize the SDK
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Evs.init(applicationContext)
// Do NOT register an IM2ResourcesResolver. The SDK reads from assets/ directly.
setContent { /* your UI */ }
}
override fun onDestroy() { Evs.stop(); super.onDestroy() }
}
Don't
- Don't register an
IM2ResourcesResolver. It's redundant on native Android. - Don't put glasses content under
res/— only underassets/.
Native iOS
Where to put files
| Kind | Location |
|---|---|
| API key | iOS app bundle root: drop sdk.key into the iOS target → "Copy Bundle Resources" |
| Glasses content | iOS app bundle: drop into the target → "Copy Bundle Resources". Use folder references (blue folders) to keep sub-paths so <App>.app/files/images/foo.png works |
| Phone-UI assets | Standard iOS — Assets.xcassets, UIImage(named:), NSLocalizedString |
Required Xcode Run Script (one-time setup)
In Xcode → your app target → Build Phases → click + → New Run Script Phase. Name it Copy Maverick AI Compose Resources and place it after "Copy Bundle Resources":
for FW in "${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Frameworks/"*.framework; do
if [ -d "$FW/compose-resources" ]; then
rsync -av "$FW/compose-resources" \
"${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/"
fi
done
In the Run Script's settings, uncheck "Based on dependency analysis" (or in the pbxproj add alwaysOutOfDate = 1; to the script phase). This silences a Xcode warning and is harmless — rsync is idempotent.
Initialize the SDK
import MaverickAI
Evs.shared.doInit()
// Do NOT register an IM2ResourcesResolver. The SDK reads from the bundle directly.
Don't
- Don't register an
IM2ResourcesResolver. It's redundant on native iOS. - Don't skip the Run Script — without it, calling
Evs.shared.showUI(...)will throwMissingResourceException ... compose-resources/....
KMP Compose Multiplatform
Where to put files
| Kind | Location |
|---|---|
| API key | composeApp/src/commonMain/composeResources/files/sdk.key. For different keys per app store: composeApp/src/androidMain/assets/sdk.key + composeApp/src/iosMain/resources/sdk.key. |
| Glasses content | 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) |
iOS build step
In your existing Compile Kotlin Framework Xcode Run Script, append :composeApp:syncComposeResourcesForIos:
cd "$SRCROOT/.."
./gradlew \
:composeApp:embedAndSignAppleFrameworkForXcode \
:composeApp:syncComposeResourcesForIos
If your project uses CocoaPods (kotlin("native.cocoapods")), the [CP] Copy Pods Resources build phase already handles this — no manual addition needed.
Required resolver (one-time setup)
Register exactly one IM2ResourcesResolver immediately after Evs.init(...) on each platform. Identical body in androidMain and iosMain:
@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 { /* your UI */ }
}
@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.
Do
- Put each file once in
commonMain/composeResources/files/...and use it from both platforms. - Use the same resolver one-liner on both source sets.
Don't
- Don't add
sourceSets["main"].assets.srcDirs("src/commonMain/composeResources/files")tocomposeApp/build.gradle.kts. It is a workaround for an outdated resolver pattern; if you have it, remove it. - Don't add a custom Xcode Run Script that
dittoscommonMain/composeResources/filesinto<App>.app/files/. Same workaround. Remove it. - Don't write a resolver that hand-builds paths like
compose-resources/composeResources/com.everysight.mav2.sdk.generated.resources/.... That points at the SDK's package, not yours; it will not find your files. - Don't skip
:composeApp:syncComposeResourcesForIos(unless you're using CocoaPods, which handles it). Without it your ownRes.drawable.*and the SDK's UI both fail on iOS.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
MissingResourceException ... compose-resources/... (native iOS) |
rsync Run Script missing or in the wrong order | Add the Run Script from the Native iOS section after "Copy Bundle Resources" |
MissingResourceException ... compose-resources/... (KMP iOS) |
syncComposeResourcesForIos not invoked |
Append :composeApp:syncComposeResourcesForIos to the Xcode Run Script (or use CocoaPods) |
| API key not found / authentication failure (KMP) | Resolver not installed, or installed in the wrong place | Install the resolver from the KMP section right after Evs.init(...) on both platforms |
M2Image / M2Font shows nothing on the glasses (KMP) |
Same as above | Same as above |
| Xcode warning "Run script build phase ... will be run during every build because it does not specify any outputs" | Resolved | Uncheck "Based on dependency analysis" on the Run Script (or set alwaysOutOfDate = 1; in pbxproj). The script is idempotent and intentionally always runs. |
Files in iosMain/resources/foo.png not found by Swift UIImage(named:) |
Files placed there land at <App>.app/compose-resources/foo.png, not the bundle root |
Either use Bundle.main.url(forResource: "foo", withExtension: "png", subdirectory: "compose-resources"), or move the file into the Xcode project under "Copy Bundle Resources" |
See Also
- API Keys —
sdk.key/app.keyfile naming, lookup order- Android Setup
- iOS Setup
- KMP Setup