Cookbook
Moving from Kotlin Multiplatform
You do not move the domain on a Monday. Undra loads next to your Kotlin and your Swift, and takes over one piece at a time.
Three steps#
| Step | What moves | What it removes |
|---|---|---|
| L1 side by side | A few pure functions (parsing, pricing, validation). The KMP module, Ktor, SQLDelight and your state holders stay. | For those functions: an Objective-C header or Alpha Swift export on the Swift side, and exceptions that end the process (KMP-1 to KMP-4). |
| L2 one feature | One screen's state and requests become a store, a query and a mutation. | The Flow collectors, the collection copies and the assembled data layer, for that feature (KMP-5, KMP-14, KMP-15). |
| L3 whole domain | expect/actual code becomes ports; persistence and the offline queue move in. | One shared framework per module, the CocoaPods dependency, and a dev loop that rebuilds the app (KMP-9, KMP-11, KMP-13). |
Row ids are those of the limitations catalogue, as of October 2026. Undra vs Kotlin Multiplatform compares the two and says where KMP is ahead.
What maps to what#
| Kotlin Multiplatform | Undra |
|---|---|
expect / actual | a port (#[undra::port]): the platform implements it, tests use a fake |
StateFlow, a ViewModel | a store's signals: a StateFlow, an @Observable, a hook |
| Ktor client | the Http port, with queries and mutations over it |
| SQLDelight, Room | Kv; a Db port over SQLite behind a feature |
suspend functions | Rust async, generated as suspend, async throws, Promise |
| sealed class errors | #[undra::error] enums |
| Compose Multiplatform | not applicable: the UI stays SwiftUI, Compose and React |
Start#
undra adopt ../MyApp writes a core crate and the exact steps for your project files, and edits none of them (the CLI). Move one function, call it from Kotlin and Swift, ship. Auth and offline-first are what L2 looks like.
What does not carry over#
- Shared UI. Compose Multiplatform has no counterpart, on purpose.
- Android has a boundary. Shared Kotlin is plain Kotlin there; Undra crosses JNI. Device numbers are not published yet.
- Generic classes do not cross as generics: generic records, enums, functions, objects and stores cross as the instantiations you list, each a named type, an overload or a class of its own, and a type parameter with no list is E0002.
- Rust is no longer your Android team's daily language.