Make your Android apps update themselves in under 60 seconds - UI changes, features, fixes, experiments. Fully within Play Store policies. Plus AI workflows that let you ship most changes by prompt instead of spinning up a release train for every tiny edit.
@KetoyEntryPoint @Composable
fun CheckoutScreen() {
val vm = ketoyViewModel<CheckoutViewModel>()
val state by vm.state.collectAsState()
Scaffold(topBar = { TopAppBar(title = { Text("Checkout") }) }) { p ->
LazyColumn(modifier = Modifier.padding(p)) {
items(state.cartItems) { item ->
ProductCard(name = item.name, price = item.price)
}
}
Button(onClick = { vm.dispatch("place_order") }) {
Text("Place Order - $" + state.total)
}
}
}
// $ ./gradlew ketoyBundle → checkout.ktx · 3.4 KBUpdate tested. Tagged. Signed. Uploaded. Now you wait. The marketing team waits. The PM refreshes the dashboard. Customer support copy-pastes “fix is coming soon.” Drag the sliders - be honest - and find out exactly how much of your life Play Store review has eaten.
play.google.com if you want a worse number.That’s a quarter of a year, gone. Refreshing the Play Console. We’re sure your team enjoyed it.
Ketoy isn’t just a runtime - it’s an update infrastructure. Most product changes (copy tweaks, layout shuffles, A/B variants, hotfixes) don’t need a four-person standup. Describe them. Ketoy plans the change, generates the bundle, signs it, and stages it for review - one workflow, no separate dev / release / ops coordination.
"Move the trust badges above the price on Checkout for India users." Ketoy plans the diff, regenerates the bundle, and queues it behind your approval gate.
Roll a variant to 5% of users, watch the metrics roll in, ramp or revert from the same console. No PM-eng-design ping-pong for every percentage bump.
AI proposes, you approve. Every bundle still gets Ed25519-signed by your release key. The autonomy lives in the planning, not in the deploy.
Dev, release, ops and ticketing for a copy change is six tickets, two reviews, and a calendar event. Ketoy collapses it into a single prompt + approval.
You add one annotation to a screen. You run ./gradlew ketoyBundle. You upload a tiny .ktx file to your CDN. Every user gets the change within 60 seconds. The app on their phone doesn’t update. The flow inside it does.
Push a fix at 3pm. Every user has it by 3:01. No Play Store release. No staged rollout timeline. No "we’ll get to it next sprint."
How the bytecode flow worksKetoy doesn’t download executable DEX. The JIT generates it on-device from KBC - exactly like ART does with bytecode. Same lane as Lua in a game engine. Play Store-compliant.
Read the security modelNo DSL to learn. No RemoteText(config = ...) replacing your Text(). No JSON schema. Just Compose. The way you already write it.
Server-driven UI tools love giving you homework. New DSLs. New schemas. New component libraries that look like Compose but aren’t. Ketoy has none of that. Here’s the list of things you don’t have to do, in order of how much we don’t want you to do them:
"fontWeight": "bold". Ever.CheckoutScreen.kt works as-is. Add one annotation.Button(). We render the real Material3 button.androidx.lifecycle.ViewModel. Real viewModelScope. SavedStateHandle still works.Ketoy isn’t a curated subset of “the easy parts of Compose.” It’s a code generator that walks your Compose classpath and writes adapters for everything it finds. When Compose adds a parameter, you run one command. Done.
All 35 Material3 composables with every parameter. Text, TextField, Button, Scaffold, LazyColumn - the works.
K2-compatible. Sealed classes, data classes, extension functions, lambdas with captures, top-level functions - all of it, compiled to KBC bytecode.
First-class. launch, async, withContext, structured concurrency, 9 Flow operators. GlobalScope stays banned - you’re welcome.
Real Android ViewModel. Real viewModelScope. Survives config changes. SavedStateHandle for process death. Hilt-friendly.
HTTP (Ktor/OkHttp), DataStore KV, Room queries, NavController push/pop/deep-link. 67 built-in capabilities. Register your own for app-specific stuff.
Drop-in. ketoy-hilt wires everything. @Inject in your ViewModels exactly like before. We added a debug overlay too - you’ll like it.
Server-driven UI gets the security question approximately once per conversation. Here is the answer, in three parts. You can copy-paste this into Slack.
Ed25519, 64-byte signature, verified before a single instruction is decoded. The public key ships in your APK. The private key stays on your signing server. Neither we nor Google can produce a valid bundle for your app. That’s the design.
The K2 plugin rejects java.io.*, kotlin.reflect, GlobalScope, and any unregistered Android API. If a malicious bundle could exist, it couldn’t compile. Forbidden calls are build errors, not surprises on a user’s phone.
KBC code can’t touch an Android API directly. Every HTTP call, every DataStore read, every NavController.navigate() goes through a registered capability. Not registered, not callable. Not callable, not exploitable.
No, and we have asked. Ketoy does not download executable DEX or JVM bytecode. The on-device JIT generates DEX locally from KBC - exactly the same operation ART performs on regular bytecode. Same legal lane as Lua scripts in a game engine, or JavaScript in a WebView. Explicitly permitted by Google’s Developer Program Policies.
Bundles are cached on-device, keyed by SHA-256. The runtime checks an ETag with your CDN; if there’s no internet, no response, no signed update - the last good bundle keeps rendering. Your app degrades to "exactly the experience it had yesterday." Acceptable, in our opinion.
Add the plugin to your app module. Write your screen the way you’d write any other Compose screen. Run ketoyBundle. Upload. Look up at the clock - yep, that fast.
plugins {
id("dev.ketoy.compiler") version "0.3.4-alpha"
}
dependencies {
implementation(platform("dev.ketoy.vm:ketoy-bom:0.3.4-alpha"))
implementation("dev.ketoy.vm:ketoy-runtime")
implementation("dev.ketoy.vm:ketoy-hilt")
implementation("dev.ketoy.vm:ketoy-adapters-material3")
ksp("dev.ketoy.vm:ketoy-ksp-processor")
}// Look familiar? It should.
@KetoyEntryPoint
@Composable
fun HomeScreen() {
val vm = ketoyViewModel<HomeViewModel>()
val state by vm.state.collectAsState()
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Text("Welcome, " + state["name"])
Button(onClick = { vm.dispatch("get_started") }) {
Text("Get Started")
}
}
}# Compile to KBC
$ ./gradlew :ketoy-screens:ketoyBundle
> Task :ketoy-screens:ketoyBundle
HomeScreen.kt → home.ktx 2.1 KB
CheckoutScreen.kt → checkout.ktx 3.4 KB
Signed: Ed25519
Compressed:Brotli q=11
# Upload. That's the whole release process.
$ aws s3 cp build/ketoy-bundles/ s3://cdn/ketoy/ \
--recursive --content-encoding br
# Total review time: 0 days. (You're welcome.)Stuck on something, evaluating Ketoy for your team, or wondering if your weird in-house SDK is supported? We answer every email. Usually within the same day. Sometimes faster than your Play Store review.
Drop us a note. Real engineers read these - no Tier-1 ticket triage, no chatbots. (We have an AI that ships code. Not one that answers email.)