Ketoy
Documentation

FAQ

What OTA and SDUI mean, where Ketoy sits between them, and how to work with it.

What is OTA (over-the-air) updating in an Android app?

OTA stands for over-the-air. An OTA update delivers new code to an app that is already installed on a device, without publishing a new APK/AAB and without the user reinstalling anything.

With Ketoy that code is real Kotlin: Jetpack Compose UI, ViewModels, business logic, navigation, data classes, and functions, compiled to a signed bundle and fetched at runtime.

What is SDUI (server-driven UI)?

SDUI stands for server-driven UI: the server decides what the screen looks like and the app renders it.

Traditional SDUI sends a JSON schema that the client maps onto a fixed catalog of components. The server can only express what that schema already supports, so a new component means a new app release anyway.

Is Ketoy OTA or SDUI?

Ketoy is an OTA framework, and SDUI is one of the things it can do.

Because Ketoy ships real Kotlin and Compose over the air, a bundle can drive the whole screen from the server, which is SDUI, and it can equally ship a ViewModel, a network call, or a pricing rule that no JSON schema could express.

OTA vs SDUI: which is better?

They answer different questions:

SDUIOTA
Question it answersWho decides the UI?How does new code reach an installed app?
PayloadJSON schemaCompiled Kotlin bytecode
CeilingThe components the schema definesAnything Kotlin and Compose can express
Logic, loops, conditionsStore releaseShipped in the bundle

JSON SDUI stops the moment a feature needs a loop, a coroutine, or business logic. OTA with Ketoy covers the SDUI use case and everything past it, because you ship compiled Kotlin instead of a schema.

Can Ketoy update only the Compose UI?

Yes. Ship a single @KetoyComposable screen and leave the rest of the app native, that is the classic SDUI setup with real Compose instead of JSON. Common starting points: marketing surfaces, onboarding, settings, and A/B-tested flows.

Can I use Ketoy for business logic only, without UI?

Yes. A bundle doesn't have to render anything. Plain Kotlin, data classes, sealed classes, when, extension functions, coroutines and Flow, runs on its own. Pricing rules, validation, feature gating, and parsing can all be updated over the air while the UI stays in the APK.

What can Ketoy update over the air?
  • Jetpack Compose UI and Material 3.
  • ViewModels and state (viewModelScope, SavedStateHandle, Hilt).
  • Navigation graphs.
  • Coroutines and Flow, structured concurrency included.
  • Networking, Room, DataStore, through capabilities.
  • Plain Kotlin, data classes, objects, sealed hierarchies, functions.

Anything that needs the host platform reaches it through the capability registry.

Do I have to migrate my code or learn a DSL to use Ketoy?

No. There is no DSL and no parallel component model. It is the Jetpack Compose and Kotlin you already write, one annotation plus one Gradle task turns an in-APK screen into an OTA-updatable one, and a custom renderer draws it as native Compose on device.

Are OTA updates allowed on the Google Play Store?

Yes. Ketoy verifies an Ed25519 signature before any code runs, and generates code on device the same way ART does with bytecode, the same idea as Lua in a game engine. Bundles are sandboxed by the capability registry, which keeps updates within Play Store policies.

What problem does Ketoy actually solve?

You ship an Android app and want to update screens, fix bugs, run A/B tests, roll out features, without going through Play Store review. Web tech (WebView, React Native via Hermes bytecode) gives you OTA updates at the cost of native look-and-feel, performance, and access to the Android platform.

Ketoy gives you OTA updates while keeping the rest:

  • Real Jetpack Compose rendering, not a wrapper.
  • Real coroutines and Flow.
  • Real Hilt / Room / DataStore / Retrofit on the host side, exposed to KBC bundles through a capability registry.
  • Signed binary bundles, Ed25519, sandboxed by capability, no remote code execution outside the sandbox.

Compose Hot Reload is for development; Ketoy is for production OTA.

Is Ketoy production-ready?

0.4.20-alpha is alpha. The wire format, opcode set, and adapter catalog are stable enough that bundles built today will load on future 0.4.x runtimes.

Plan accordingly. Use it for parts of your app that benefit most from OTA, settings screens, marketing surfaces, A/B-tested flows, while keeping safety-critical paths (auth, payment, crash-recovery) native.

Where do KBC bundles run?
  • Android only today. Compose Multiplatform support is on the roadmap; KMP-iOS execution is also in roadmap.
  • API 26+ required for the JIT (enableJIT = true + a dexCacheDir). Below 26 the interpreter runs everything.
  • JDK 17 to build (the compiler plugin runs in the Kotlin compiler, which needs JDK 17).
How big are bundles?
Screen complexityTypical signed .ktx size
Mid-size todo screen with ViewModel + Room3.5 KB
Multi-screen surface8–20 KB

Brotli compression on the code section gets 2–3× over raw bytecode.

How fast is the interpreter?

The microbenchmark suite (:ketoy-benchmark) verifies:

  • COMPOSABLE_CALL adapter dispatch overhead: < 0.5 ms per call.
  • Bundle parse + validate: < 50 ms for typical screens.
  • Tier-1 JIT speedup over interpreter on pure-logic functions: ≥ 1.5×.

In practice, most screens have negligible KBC overhead, recomposition and layout dominate.

Can KBC use Compose Hot Reload?

Not yet directly. The dev flow today is:

  1. Edit @KetoyComposable source.
  2. ./gradlew :app:ketoyBundle :app:installDebug.
  3. Relaunch the screen.

A future tooling pass will support bundle hot-swap via remote delivery (push the new .ktx to a dev server; the runtime swaps it without restarting the app).

Why Ed25519 and not RSA / ECDSA?

Ed25519:

  • 64-byte signatures (fixed-size).
  • Fast verification (~50 µs on modern phones).
  • No randomness during signing, deterministic, reproducible builds.
  • Cryptographically modern (Curve25519 family).

RSA is too large (256+ byte signatures) and slow to verify. ECDSA requires a CSPRNG at signing time. Ed25519 is the right primitive for this workload.

Do bundles work offline?

Yes. The runtime caches remote bundles at context.cacheDir/ketoy_bundles/<sha256(url)>.ktx. On network failure, it falls back to the cached copy. For bundles shipped in the APK (KetoyBundleSource.Asset(...)), there's never a network call.

How do I update a bundle for some users but not others?

Two options:

  1. Server-side: serve different .ktx files based on user segmentation (device ID, region, A/B cohort). Standard CDN / feature-flag service patterns.
  2. minAppVersion: gate bundle activation against PackageInfo.longVersionCode. Newer bundles ignored by older APKs.

A/B testing is typically server-side delivery (cohort A gets URL A, cohort B gets URL B).

Can a KBC bundle access user files / SAF?

Not directly. Wrap Storage Access Framework calls as Custom Capabilities:

kotlin
registerSuspend(AppCapabilityIds.PICK_DOCUMENT) { _ ->
    saf.pickAndReadDocument()       // host-side, real ContentResolver
}

The KBC bundle calls pickDocument(): String, gets back the file contents. The file picker UI runs native.

Can KBC bundles include native code (NDK / JNI)?

No. KBC is interpreted bytecode + a Tier-1 DEX JIT for pure-logic functions. No .so / .aar shipping inside the bundle. Native code ships in the host APK; KBC reaches it via capabilities.

Does Ketoy replace Hilt / Room / Retrofit?

No. It bridges them. The host APK uses Hilt / Room / Retrofit normally; the KBC bundle declares @KetoyCapabilityStub functions that the compiler resolves to INVOKE_CAPABILITY opcodes. The runtime then dispatches to the host-side KetoyCapabilityProvider-registered lambda.

See Hilt, Room, Networking.

How do I debug a KBC bundle on-device?

Three approaches:

  1. Dev overlay, KetoyConfig.enableDevOverlay = true (auto-set in debug builds via Hilt). Render KetoyDevOverlay(devEvents = vm.devEvents) on top of your KBC screen. Shows last 5 COMPOSABLE_CALL, CONSTRUCT_JVM, and CAPABILITY dispatches with names + timing.
  2. ketoy analyze, dump the bundle's manifest, strings, and opcode listing.
  3. adb logcat, the runtime logs Ketoy, KetoyBundleLoader, KetoyBC tags.

Source-level breakpoints in KBC source are not yet supported (the IDE plugin is roadmap).

Why does my screen render the native fallback instead of the KBC bundle?

KetoyScreen renders nativeFallback whenever loading or executing the bundle fails. Common causes:

  • The bundle asset is missing or empty.
  • Signature verification failed (wrong public key, modified bundle).
  • The entry-point name doesn't match.
  • A capability the bundle declares isn't registered host-side.
  • The runtime threw during interpretation (e.g. cast failure, missing resolver).

Check adb logcat for the actual exception. The fallback exists so a broken bundle never crashes your app, it's a graceful degradation path, not a sign of misconfiguration to ignore.

My `.ktx` size keeps growing. What's eating bytes?

Roughly:

  • String pool, every distinct literal in your KBC source (text, FQ names, capability names, modifier test tags).
  • Function table, one entry per KBC function (your composables + helpers + lambdas + closure-converted captures).
  • Code, Brotli-compressed bytecode.
  • Modifier table, one entry per unique modifier chain.

To shrink:

  • Hoist repeated strings into top-level const vals, they pool once.
  • Reuse modifier chains, val rowMod = Modifier.fillMaxWidth().padding(8.dp); Row(modifier = rowMod) { … }.
  • Avoid huge when arms in a single function, split into helpers.

Use ketoy analyze --strings to dump the pool.

Can I write KBC code in a separate module from the host APK?

Yes, but it's optional. Two patterns:

  1. In-tree (default for 0.3.x), KBC source lives in the host :app module under a subpackage. ketoy { exportFromAppModule = true } attaches the compiler plugin to one variant. This is what the CLI's ketoy init sets up.
  2. Separate KBC module, a Kotlin/Android library module with id("dev.ketoy.compiler") applied. The plugin emits .ktx to build/ketoy-bundles/; the host module copies (or downloads) it into assets.

Pattern 1 is faster to iterate. Pattern 2 is cleaner for multi-feature apps.

What's the relationship between Ketoy and Compose Multiplatform?

Ketoy targets Android Compose today. Compose Multiplatform is JetBrains' own multi-target Compose runtime. They aren't the same thing. Ketoy is an OTA delivery + sandbox layer for Compose on Android; Compose Multiplatform is a multi-platform UI toolkit.

Future work might bring Ketoy bundles to iOS via Compose Multiplatform, but it's not in 0.4.x.

Where do I file bugs / feature requests?
  • File Issue and Request Feature Request: https://ketoy.dev/issue (use the Bug Report / Feature Request).
  • Crashes: include adb logcat output, the bundle (or ketoy analyze --json output), and minimum repro steps.
I'm stuck. Where else can I look?
  • Supported features, the canonical what-works catalog.
  • Architecture, the deeper internals.
  • Contact Ketoy Support, open an Issue or Contact Ketoy Support, links are in the Bottom of this Page and in the home page bottom.