Definitions
Definitions declare how Koin creates and manages your dependencies. This guide covers all definition types using both DSL and Annotations.
Definition Types
| Type | DSL | Annotation | Lifecycle | Use Case |
|---|---|---|---|---|
| Singleton | single() | @Singleton | One instance for app lifetime | Services, repositories, databases |
| Factory | factory() | @Factory | New instance each time | Presenters, use cases, stateful objects |
| Scoped | scoped() | @Scoped | One instance per scope | Activity-bound, session-bound objects |
| ViewModel | viewModel() | @KoinViewModel | Android ViewModel lifecycle | ViewModels |
Declaring Definitions
Compiler Plugin DSL (Recommended)
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
| Concept | Compiler Plugin DSL | Classic DSL | Annotation |
|---|---|---|---|
| Singleton | single<MyClass>() | singleOf(::MyClass) | @Singleton / @Single |
| Factory | factory<MyClass>() | factoryOf(::MyClass) | @Factory |
| Scoped | scoped<MyClass>() | scopedOf(::MyClass) | @Scoped |
| ViewModel | viewModel<MyVM>() | viewModelOf(::MyVM) | @KoinViewModel |
| Worker | worker<MyWorker>() | workerOf(::MyWorker) | @KoinWorker |
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)
}
Best Practices
- Prefer Constructor Injection - Makes code testable without Koin
- Use
singlefor stateless services - Repositories, clients, helpers - Use
factoryfor stateful objects - Presenters, use cases with state - Use
scopedfor lifecycle-bound objects - Activity, Fragment, Session - Minimize qualifiers - Use different interfaces instead when possible
- Bind to interfaces - Depend on abstractions, not implementations
- Use
create(::builder)for external libraries - Safer dependency resolution
Next Steps
- Injection - Retrieve dependencies
- Qualifiers - Named and typed qualifiers
- Advanced Patterns - Collections, decorators, external libraries
- Scopes - Manage lifecycle