Skip to main content
Version: 4.2 - LTS

Definitions

Definitions declare how Koin creates and manages your dependencies. This guide covers all definition types using both DSL and Annotations.

Definition Types​

TypeDSLAnnotationLifecycleUse Case
Singletonsingle()@SingletonOne instance for app lifetimeServices, repositories, databases
Factoryfactory()@FactoryNew instance each timePresenters, use cases, stateful objects
Scopedscoped()@ScopedOne instance per scopeActivity-bound, session-bound objects
ViewModelviewModel()@KoinViewModelAndroid ViewModel lifecycleViewModels

Declaring Definitions​

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

val appModule = module {
// Singleton
single<Database>()
single<UserRepository>()

// Factory - new instance each time
factory<UserPresenter>()

// ViewModel
viewModel<UserViewModel>()
}

Annotations​

@Singleton  // or @Single
class Database

@Singleton
class UserRepository(private val database: Database)

@Factory
class UserPresenter(private val repository: UserRepository)

@KoinViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel()

Classic DSL​

val appModule = module {
// With constructor reference (autowiring)
singleOf(::Database)
singleOf(::UserRepository)
factoryOf(::UserPresenter)
viewModelOf(::UserViewModel)

// With lambda (manual wiring)
single { Database() }
single { UserRepository(get()) }
factory { UserPresenter(get()) }
viewModel { UserViewModel(get()) }
}

Definition Comparison​

ConceptCompiler Plugin DSLClassic DSLAnnotation
Singletonsingle<MyClass>()singleOf(::MyClass)@Singleton / @Single
Factoryfactory<MyClass>()factoryOf(::MyClass)@Factory
Scopedscoped<MyClass>()scopedOf(::MyClass)@Scoped
ViewModelviewModel<MyVM>()viewModelOf(::MyVM)@KoinViewModel
Workerworker<MyWorker>()workerOf(::MyWorker)@KoinWorker
info

The compiler plugin is analysing your class and function parameters, to generate the right call to Koin with get() function you don't have to write anymore.

Single (Singleton)​

Creates one instance that's reused throughout the app:

// DSL
single<DatabaseHelper>()

// Annotation
@Singleton
class DatabaseHelper

Both create the same result - a single instance shared across all consumers.

Factory​

Creates a new instance each time:

// DSL
factory<UserPresenter>()

// Annotation
@Factory
class UserPresenter(private val repository: UserRepository)

Scoped​

Creates one instance per scope:

// DSL
scope<MyActivity> {
scoped<ActivityPresenter>()
}

// Annotation
@Scoped(MyActivityScope::class)
class ActivityPresenter

ViewModel​

Android ViewModel with proper lifecycle:

// DSL
viewModel<UserViewModel>()

// Annotation
@KoinViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel()

Interface Binding​

Compiler Plugin DSL​

single<UserRepositoryImpl>() bind UserRepository::class

// Multiple bindings
single<MyServiceImpl>() binds arrayOf(ServiceA::class, ServiceB::class)

Classic DSL​

singleOf(::UserRepositoryImpl) bind UserRepository::class

// Or with lambda
single<UserRepository> { UserRepositoryImpl(get()) }

Annotations​

Interface binding is automatic when your class implements an interface:

@Singleton
class UserRepositoryImpl(
private val database: Database
) : UserRepository // Automatically binds to UserRepository

For explicit binding:

@Singleton
@Binds(UserRepository::class)
class UserRepositoryImpl : UserRepository

Qualifiers (Named Definitions)​

When you have multiple definitions of the same type. See also Injection with Qualifiers for retrieval.

Compiler Plugin DSL​

With Compiler Plugin DSL, you need to annotate with @Named to use string qualifier (like you were previously using named())

@Named("local")
class LocalDatabase : Database

@Named("remote")
class RemoteDatabase : Database

class UserRepository(
@Named("local") private val localDb: Database,
@Named("remote") private val remoteDb: Database
)

single<LocalDatabase>()
single<RemoteDatabase>()
single<UserRepository>()

// Usage
val localDb: Database = get(named("local"))

Classic DSL​

single<Database>(named("local")) { LocalDatabase() }
single<Database>(named("remote")) { RemoteDatabase() }

// Usage
val localDb: Database = get(named("local"))

Annotations​

@Singleton
@Named("local")
class LocalDatabase : Database

@Singleton
@Named("remote")
class RemoteDatabase : Database

// In a consumer
@Singleton
class UserRepository(
@Named("local") private val localDb: Database,
@Named("remote") private val remoteDb: Database
)

Injected Parameters​

Pass parameters at injection time:

Compiler Plugin DSL​

Use the @InjectedParam to indicate that a parameter will be served by injected parameters.

class UserPresenter(
@InjectedParam userId : String,
repository : UserRepository
)

factory<UserPresenter>()

Classic DSL​

class UserPresenter(
userId : String,
repository : UserRepository
)

factory { params ->
UserPresenter(
userId = params.get(),
repository = get()
)
}

Annotations​

@Factory
class UserPresenter(
@InjectedParam val userId: String,
val repository: UserRepository // Auto-injected
)

// Usage
val presenter: UserPresenter = get { parametersOf("user123") }

Optional Dependencies​

Compiler Plugin DSL​

class MyService(
val required: RequiredDep,
val optional: OptionalDep? // Resolved with getOrNull()
)

single<MyService>()

Classic DSL​

single {
MyService(
required = get(),
optional = getOrNull()
)
}

Annotations​

Nullable parameters are handled automatically:

@Singleton
class MyService(
val required: RequiredDep,
val optional: OptionalDep? // Resolved with getOrNull()
)

Lazy Injection​

Defer instance creation:

Compiler Plugin DSL​

class MyService(
val lazyDep: Lazy<HeavyDependency> // Deferred creation
)

single<MyService>()

Classic DSL​

single {
MyService(
lazyDep = inject() // Lazy<Dependency>
)
}

Annotations​

@Singleton
class MyService(
val lazyDep: Lazy<HeavyDependency> // Deferred creation
)

Properties​

Inject configuration values:

Compiler Plugin DSL​

class ApiClient(
@Property("api_url") val url: String,
@Property("api_key") val key: String
)

single<ApiClient>()

Classic DSL​

single {
ApiClient(
url = getProperty("api_url"),
key = getProperty("api_key", "default")
)
}

Annotations​

@Singleton
class ApiClient(
@Property("api_url") val url: String,
@Property("api_key") val key: String
)

Callbacks​

onClose Callback​

Execute code when instance is released:

single {
Database()
} onClose {
it?.close() // Called when Koin stops or scope closes
}

createdAtStart​

Create instance eagerly at startup:

// Compiler Plugin DSL
single<ConfigManager>() withOptions {
createdAtStart()
}

// Classic DSL
single(createdAtStart = true) {
ConfigManager()
}

Definition Override​

Default: Last Wins​

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

val testModule = module {
single<ApiService> { MockApi() } // Overrides production
}

startKoin {
modules(prodModule, testModule)
}

Explicit Override​

In strict mode, mark overrides explicitly:

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

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

Safe DSL Patterns​

The Koin Compiler Plugin transforms DSL definitions at compile time — auto-wiring constructor parameters and validating them. Here are the key patterns:

Function Builders with create()​

Use create(::function) to wrap external libraries you don't own. The function parameters are auto-resolved from the DI container:

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

// Builder functions — parameters resolved by Koin
fun database(context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "my-db").build()

fun topicDao(db: AppDatabase): TopicDao = db.topicDao()
fun newsDao(db: AppDatabase): NewsResourceDao = db.newsResourceDao()

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

This is the recommended pattern for Room databases, Retrofit services, OkHttp clients, and other external libraries.

Module Composition with includes()​

Organize modules by layer and compose them:

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

val networkModule = module {
includes(dispatchersModule)

single { create(::json) }
single<AppHttpClient>()
single<DemoNetworkDataSource>() bind NetworkDataSource::class
}

private fun json(): Json = Json { ignoreUnknownKeys = true }

App Module — Composing Everything​

The app module includes all feature modules and declares ViewModels and use cases:

import org.koin.dsl.module
import org.koin.plugin.module.dsl.*
import org.koin.androidx.scope.dsl.activityScope

val appModule = module {
includes(
dispatchersModule,
databaseModule,
dataStoreModule,
networkModule,
dataModule,
syncModule
)

// Domain use cases — factory (new instance each time)
factory<GetFollowableTopicsUseCase>()
factory<GetSearchContentsUseCase>()

// ViewModels
viewModel<MainActivityViewModel>()
viewModel<HomeViewModel>()
viewModel<BookmarksViewModel>()

// Activity-scoped definitions
activityScope {
scoped<ActivityTracker>()
}
}

Custom Qualifiers in DSL​

Qualifier annotations work with create(::function) too:

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

val dispatchersModule = module {
single { create(::dispatcherIO) }
single { create(::dispatcherDefault) }
single { create(::coroutineScope) }
}

@Dispatcher(NiaDispatchers.IO)
fun dispatcherIO(): CoroutineDispatcher = Dispatchers.IO

@Dispatcher(NiaDispatchers.Default)
fun dispatcherDefault(): CoroutineDispatcher = Dispatchers.Default

fun coroutineScope(
@Dispatcher(NiaDispatchers.Default) default: CoroutineDispatcher
) = CoroutineScope(SupervisorJob() + default)

Worker with DSL​

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

val syncModule = module {
single<WorkManagerSyncManager>() bind SyncManager::class
worker<SyncWorker>()
}

Complete Pattern: Repository with Interface Binding​

import org.koin.dsl.module
import org.koin.dsl.bind
import org.koin.plugin.module.dsl.single

val dataModule = module {
includes(databaseModule, dataStoreModule, networkModule)

single<OfflineFirstNewsRepository>() bind NewsRepository::class
single<OfflineFirstTopicsRepository>() bind TopicsRepository::class
single<OfflineFirstUserDataRepository>() bind UserDataRepository::class
}

All these definitions are validated at compile time by the Koin Compiler Plugin — missing dependencies, qualifier mismatches, and broken call sites are caught at build time. See Compile-Time Safety.

Best Practices​

  1. Prefer Constructor Injection - Makes code testable without Koin
  2. Use single for stateless services - Repositories, clients, helpers
  3. Use factory for stateful objects - Presenters, use cases with state
  4. Use scoped for lifecycle-bound objects - Activity, Fragment, Session
  5. Minimize qualifiers - Use different interfaces instead when possible
  6. Bind to interfaces - Depend on abstractions, not implementations
  7. Use create(::builder) for external libraries - Safer dependency resolution

Next Steps​