Skip to main content
Version: 4.2

Definitions

A definition tells Koin how to create one type and how long to keep the instance. This page covers the definition types, interface binding, how constructor parameters are resolved, and definitions for classes you don't own. It assumes you know what a module is (see Core Concepts).

Code samples come in three styles. Pick yours in the tabs, the choice applies to every page. If you are not sure which style to use, read Choosing your style.

Definition types​

TypeInstanceTypical use
singleOne instance, kept for the lifetime of the Koin containerRepositories, API clients, databases
factoryA new instance each time it is requestedPresenters, use cases, short-lived objects
scopedOne instance per scope, released when the scope closesObjects bound to an Activity, a session, a request
viewModelManaged by the ViewModel lifecycleViewModels (Android and Compose Multiplatform)
workerCreated by WorkManagerAndroid ListenableWorker

Declaring them:

import org.koin.dsl.module
import org.koin.plugin.module.dsl.*

val appModule = module {
single<Database>()
single<UserRepository>()
factory<UserPresenter>()
viewModel<UserViewModel>()
}

With the Compiler Plugin DSL and annotations, you never write get(). The Koin Compiler Plugin reads the constructor and generates the calls for you, then checks at build time that every dependency is declared.

single and factory are explained above. scoped needs a scope declaration: see Scopes. viewModel has its own page: see ViewModel. worker is covered in WorkManager.

Single or factory?​

Use single when the object has no per-caller state and is costly to create (a database, an HTTP client). Use factory when each caller needs its own instance, or the object holds state that must not be shared. If an instance should live longer than a factory but shorter than the app, you need a scope.

Interface binding​

Most of the time, consumers depend on an interface, not on the implementation. Bind the implementation to the interface:

val dataModule = module {
single<UserRepositoryImpl>() bind UserRepository::class

// several types at once
single<MyServiceImpl>() binds arrayOf(ServiceA::class, ServiceB::class)
}

A consumer then asks for UserRepository and gets the UserRepositoryImpl instance. When two definitions bind the same type, you need a qualifier to tell them apart.

Constructor parameters​

Koin resolves each constructor parameter from the container. The parameter type decides how:

ParameterResolved asDetail
repository: UserRepositoryThe UserRepository definition. Fails if it is missing
logger: Logger?The definition if it exists, null otherwise
heavy: Lazy<HeavyService>Created on first access of heavy.valueInjection
loggers: List<Logger>All definitions bound to LoggerAdvanced Patterns
@Named("local") db: DatabaseThe definition with that qualifierQualifiers
@InjectedParam userId: StringA value passed by the caller at resolution timeInjected Parameters
@Property("api_url") url: StringA Koin propertyStarting Koin

With the Compiler Plugin DSL and annotations, this works from the constructor signature alone:

class SyncService(
private val repository: UserRepository,
private val analytics: Analytics?,
private val uploader: Lazy<Uploader>,
)

val appModule = module {
single<SyncService>()
}

Classes you don't own​

Classes from libraries (Room, Retrofit, OkHttp) can't be annotated and often come from a builder. Declare them with a function whose parameters Koin resolves:

import org.koin.plugin.module.dsl.create

fun database(context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app-db").build()

fun topicDao(db: AppDatabase): TopicDao = db.topicDao()

val databaseModule = module {
single { create(::database) }
single { create(::topicDao) }
}

create(::function) resolves the function parameters like a constructor, with the same compile-time checks. Qualifier annotations on the function and its parameters work too.

For complete Retrofit, Room and serialization setups on Android, see Android Library Integration.

Eager creation​

Koin creates a single the first time someone asks for it. To create it when Koin starts instead, mark it createdAtStart:

val appModule = module {
single<ConfigManager>() withOptions {
createdAtStart()
}
}

Eager creation runs on the thread that starts Koin. On Android this is the main thread during Application.onCreate(), so every eager definition adds to app startup time. Keep it for objects that must exist before the first screen. To move work off startup, see Lazy Modules.

Release callback​

onClose runs when the instance is released: when Koin stops for a single, or when its scope closes for a scoped.

val appModule = module {
single<Database>() onClose { it?.close() }
}

onClose is never called for a factory: Koin does not keep factory instances.

Overriding a definition​

When two modules declare the same type, the module loaded last wins:

val prodModule = module {
single<ApiService> { ProductionApi() }
}

val testModule = module {
single<ApiService> { FakeApi() }
}

startKoin {
modules(prodModule, testModule) // ApiService resolves to FakeApi
}

This is useful in tests, and a source of bugs when it happens by accident. To make overrides explicit, disable them globally and mark the ones you want:

val testModule = module {
single<ApiService> { FakeApi() }.override()
}

startKoin {
allowOverride(false)
modules(prodModule, testModule)
}

With allowOverride(false), an unmarked duplicate fails at start.