Ketoy 0.4.20-alpha: back on Maven Central
This is the first Maven Central release since 0.4.13-alpha. It ships
the headline app-scoped shared runtime (shared ViewModels across
screens) plus everything that landed in the 0.4.16-0.4.19 mavenLocal
iterations, Material3 AlertDialog, FlowRow, Compose Pagers, and a batch
of fixes.
Coordinates
dependencies {
implementation(platform("dev.ketoy.vm:ketoy-bom:0.4.20-alpha"))
implementation("dev.ketoy.vm:ketoy-runtime")
implementation("dev.ketoy.vm:ketoy-hilt") // optional
implementation("dev.ketoy.vm:ketoy-koin") // optional (new)
implementation("dev.ketoy.vm:ketoy-adapters-material3")
}Drop-in upgrade from 0.4.x, everything below is additive and backward
compatible. No wire-format, opcode, or @KetoyViewModel authoring change.
At a glance
| Area | What's new |
|---|---|
| Shared runtime | KetoyRuntimeHost { ... }, one bundle, one KetoyVM, ViewModels shared across screens |
| Per-screen opt-out | KetoyScreen(entryPoint, useSharedRuntime = false) for argument-scoped screens |
| Dialogs | Material3 AlertDialog (confirm / info) |
| Layout | FlowRow (real androidx.compose.foundation.layout.FlowRow) |
| Paging | HorizontalPager / VerticalPager with real snap / fling |
| Defaults | TextField / OutlinedTextField / Switch .enabled default to true |
| DI | New ketoy-koin module (Koin integration, alongside ketoy-hilt) |
App-scoped shared runtime
Until now each KetoyScreen loaded the bundle independently, every route
got its own KetoyVM, its own heap, and its own ViewModel instances. Two
screens could not share a @KetoyViewModel or any state.
KetoyRuntimeHost fixes that. Wrap your NavHost (or any screen group) in
it: it loads and verifies the .ktx once into a single Activity-scoped
KetoyVM, and every KetoyScreen inside resolves against that shared
runtime.
@Composable
fun App() {
KetoyRuntimeHost(bundleSource = KetoyBundleSource.Asset("ketoy/main.ktx")) {
val nav = rememberNavController()
NavHost(nav, startDestination = "home") {
composable("home") { KetoyScreen(entryPoint = "HomeScreen") }
composable("upcoming") { KetoyScreen(entryPoint = "UpcomingScreen") }
composable("search") { KetoyScreen(entryPoint = "SearchScreen") }
composable("details/{id}") {
// argument-scoped, keep a fresh per-nav-entry ViewModel
KetoyScreen(entryPoint = "DetailsScreen", useSharedRuntime = false)
}
}
}
}Now ketoyViewModel<CatalogViewModel>() called from Home, Upcoming, and
Search returns the same instance with a single lifecycle. Data one tab
fetches in the VM's init is reused by the others, Upcoming appears
instantly because CatalogViewModel already loaded its slice for Home.
Shared vs per-screen, the duo
| Scope | How | When to use |
|---|---|---|
| Shared (activity-scoped) | Default under KetoyRuntimeHost | Cross-tab / cross-screen state, a catalog, a session, a cart |
| Per-screen (per-nav-entry) | KetoyScreen(..., useSharedRuntime = false) | Argument-scoped screens, a details page keyed by an id |
Both coexist. A shared CatalogViewModel for the browse tabs and a
per-screen DetailsViewModel keyed by show id is the reference pattern.
What you keep
Native navigation, the real NavHost, back
stack, deep links, and transitions are untouched.
Real lifecycle, shared VMs live through config change and
are cleared with the Activity (real Android ViewModelStore);
per-screen VMs are cleared with their nav entry. One runtime,
a single parse, heap, and coroutine engine for the whole app instead of
one per screen.
No KetoyRuntimeHost? KetoyScreen behaves exactly as in 0.4.x (per-screen
runtime). Adopt the host only where you want sharing, a full architecture
decision record covers the ComposeEngine-decoupling refactor that makes
one shared KetoyVM safe to drive many concurrent screen compositions.
Also shipping (from the 0.4.16-0.4.19 iterations)
- Material3
AlertDialog, the reusable confirm / info dialog. Nullable content slots (dismissButton/icon/title/text) pass through, so an absent slot renders no button;shape, content colours, andtonalElevationhonour-default toAlertDialogDefaults.*. Security-sensitive flows (e.g. a captcha-gated destructive action) can stay native and be triggered from KBC through a host capability. FlowRow, the realandroidx.compose.foundation.layout.FlowRow(off the deprecated accompanist wrapper) for wrapping chip / tag rows.- Compose Pagers,
HorizontalPager/VerticalPageras real KSP-generated catalog adapters, with genuine snap / fling / paging:
HorizontalPager(state = rememberPagerState(pageCount = { items.size })) { page ->
HeroCard(items[page])
}The pageCount lambda is extracted and re-run reactively, so the page
count tracks your backing data. No new opcode or wire change.
- Sensible input defaults,
TextField/OutlinedTextFieldandSwitchnow default.enabledtotruewhen omitted (previouslyfalse, a disabled control), so a plain text field accepts input withoutenabled = true. ketoy-koin, a new optional Koin DI integration module, alongside the existingketoy-hilt.
Fixes rolled in
list.size/map.size/array.sizereads inside a KBC body no longer crash (NoSuchMethodException: getSize). Reflection now falls back to the Kotlin.sizeforCollection/Map/Array.COMPOSABLE_CALLfreezes the caller's register file, soRegister-typed params and captures insideforEach/forEachIndexedbodies see per-iteration values, fixes anIndexOutOfBoundsrenderingforEach-emitted lists.Collection.isNotEmpty()/Map.isNotEmpty()in a KBC body resolve via a reflection fallback instead of crashing.
Migrating
Nothing to change in existing 0.4.x screens. To adopt cross-screen shared
ViewModels, wrap your NavHost in KetoyRuntimeHost(...) and add
useSharedRuntime = false to any argument-scoped screen.