Skip to main content
Version: 4.2

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