Kodein Open Source Initiative documentation

The Kodein Open Source Initiative is a suite of libraries that aims to help you to develop Kotlin Multiplatform applications.

Libraries

Inara: Dependency Injection

Inara turns a plain Kotlin interface into a dependency-injection module. You declare what a module offers as ordinary interface members; you decide how each member is produced by supplying a Binding at construction time. Inara plugs and validates everything at compile time, then gets out of your way.

There is no separate "component" type, no qualifier annotations, no get<T>() lookups. Consumers call module.httpClient() — a normal method on a normal interface.

  • Opinionated, by design. No global context, no mutable context, and business code is always independent from the DI layer — it has no notion that Inara even exists. @DIFactory takes that further still, generating a class with zero imports from Inara at all.

  • Wiring is a value, not a configuration language. A module instance is built by calling a generated function with one Binding per member. Bindings are ordinary values you can pass around, store, and swap.

  • Every dependency is checked by the compiler. Forget a binding and the builder call doesn’t compile. There is no runtime "unresolved dependency" failure mode.

  • Bodies see the whole module. A Binding's lambda runs with the module as its receiver, so any member can reach any sibling member — including inherited ones — without you wiring anything up:

    Binding.single { HttpClient(this.baseUrl) }   // `baseUrl` is this module's own member
  • suspend members are native. Mark a member suspend and it is bound with a SuspendBinding, whose every factory takes a suspending body. The generated override is itself suspend, so asynchronous construction propagates the way it should instead of hiding behind runBlocking or a`Deferred` you have to unwrap at every call site.

    suspend fun token(): Token   // bound with SuspendBinding.single { authClient.fetchToken() }
  • Overriding is first-class. Every module gets generated copy(…​) overloads, so building a test variant means overriding one member and inheriting the rest.

  • Multiplatform, with no platform-specific surprises. Inara targets every platform it supports and behaves exactly the same on each of them — the sole exception is the JS/Wasm targets, where thread synchronization is a no-op, since there is no thread to synchronize.

Nomi: REST API

Nomi turns plain Kotlin interfaces into a working REST API. You declare resources — nested interfaces that are your routes — and services — interfaces whose suspend functions are your endpoints. A KSP processor generates a ktor server implementation from them; another generates a typed ktor client.

There is no reflection, no runtime route table and no hand-written URL on either side.

  • One declaration, both sides. The resource and service interfaces in commonMain are the only place the API is written down. The server’s routes and the client’s implementation are both generated from them, so they can never disagree about a path, a method, or a type.

  • A route is a type, not a string. Nesting @Endpoint.Resource interfaces is the path structure, and a path parameter is a val on the interface that owns it.

  • Business failures are in the return type. Every endpoint returns Either<V, E>: the server answers a failure with Either.error(…​), and the client receives it back as the very same, typed E. No exception crosses the wire for a business outcome.

  • Everything is checked before anything is generated. A processor validates every declaration it reads, and generates nothing at all if anything is invalid. There is no partial output and no runtime "route not found".

  • The API documents itself. Set withOpenApiSpecs and ktor serves an OpenAPI document straight from the routing tree.

  • Serialization stays yours. Nomi defines the shape of a call and hands the body to ktor’s ContentNegotiation: JSON, CBOR, ProtoBuf, or anything else ktor can negotiate.

  • Multiplatform client, JVM server. The generated client runs on every target; the generated server runs on ktor-server, which is JVM-only.

VIE: Model-View-Intent

VIE brings Model-View-Intent to Compose Multiplatform: one state flow down, intents up, signals sideways. A screen has three types: a Model that the view renders, Intents the view posts (usually user events), and Signals, which are one-off things the view reacts to but does not render, such as a navigation or a snackbar. You declare all three in an interface, a VIEViewModel implements it, and a VIEView renders it.

No navigation framework, no event bus, no manual collectAsState.

  • The contract is an interface. The view depends on the VIE interface, not on the ViewModel that implements it, so you can render it in a preview or a test by giving it a different receiver.

  • The view never holds the ViewModel. Inside VIEView, the view sees a VIEViewScope with only two members: post(intent) and signalFlow. It cannot read the model flow, call update, or reach viewModelScope.

  • Events and Effects are different delivery channels. An Event is broadcast to every view that is listening. An Effect reaches exactly one view, so a "navigate back" fires once even when two views observe the same ViewModel.

  • Model updates are serialized. update { } hands its block to a single consumer and suspends until it is applied, so concurrent updates never lose one another.

  • Built on ViewModel, not beside it. VIEViewModel extends androidx.lifecycle.ViewModel, so viewModelScope, viewModel { } and Navigation3 entry scoping work unchanged.

  • Middlewares wrap the flows. A VIEMiddleware intercepts the model, intent and signal flows as plain Flow transformations (logging, filtering, inspecting) without the ViewModel or the view knowing.

MocKMP: Mocking for tests

MocKMP turns a plain interface or abstract class into a mock or a fake for your tests. You ask for what a test needs with an annotation — @Mock, @Fake, @UsesMocks, @UsesFakes — and a KSP processor generates the implementation, ready to inject.

  • Compile-time, not reflection. A KSP processor generates a real class implementing the interface or abstract class you asked to mock or fake, so it behaves identically on every target — including the ones with no runtime reflection at all, like iOS, JS and Wasm.

  • Mocking and faking answer different questions. A mock's behaviour is defined per test, and every call on it can be verified; a fake's data is inert and zero-valued, and nothing about it is checked. Reach for a fake when a collaborator only needs to exist, and a mock when the test needs to configure or verify it.

  • Nothing is generated until you ask for it. @UsesMocks / @UsesFakes — or a @Mock / @Fake property — requests exactly the types a given test needs, so an interface used across a large codebase doesn’t grow generated code everywhere it’s declared, only where it’s actually mocked or faked.

  • Injection kills the constructor boilerplate. Annotate properties @Mock or @Fake and call`mocker.injectMocks(this)`; inherited properties are injected too, so a shared base test class works exactly as if its mocks had been declared locally.

  • The TestsWithMocks helper collapses the ceremony further. Inherit it and get a Mocker, an automatic reset before every test, withMocks property delegates rebuilt fresh each time, and every / everySuspending / verify / verifyWithSuspend callable without the mocker. prefix.

  • Constraints read the same in a definition and in a verification. isAny, isEqual, isNotNull, isInstanceOf, and any custom constraint you write with isValid, work identically whether they appear inside every or inside verify — one matcher vocabulary, not two.

  • Multiplatform, with no platform-specific surprises. mockmp-runtime targets every platform MocKMP supports and behaves the same on each of them.