Definitions
Definitions declare how Koin creates and manages your dependencies. This guide covers all definition types using both DSL and Annotations.
Definition Types
| Type | Lifecycle | Use Case |
|---|---|---|
| Single | One instance for app lifetime | Services, repositories, databases |
| Factory | New instance each time | Presenters, use cases, stateful objects |
| Scoped | One instance per scope | Activity-bound, session-bound objects |
| ViewModel | 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 |
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:
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:
DSL
factory<UserPresenter>() // With Compiler Plugin - auto-detected
// Classic DSL
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
DSL
// Classic DSL - use getOrNull()
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:
DSL
single {
MyService(
lazy = inject() // Lazy<Dependency>
)
}
Annotations
@Singleton
class MyService(
val lazyDep: Lazy<HeavyDependency> // Deferred creation
)
Properties
Inject configuration values:
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:
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)
}
Complete Examples
Clean Architecture with DSL
val dataModule = module {
single<Database>()
single<ApiClient>()
single<UserRepositoryImpl>() bind UserRepository::class
}
val domainModule = module {
factory<GetUserUseCase>()
factory<UpdateUserUseCase>()
}
val presentationModule = module {
viewModel<UserListViewModel>()
viewModel<UserDetailViewModel>()
}
Clean Architecture with Annotations
// Data layer
@Singleton
class Database
@Singleton
class ApiClient
@Singleton
class UserRepositoryImpl(
private val database: Database,
private val api: ApiClient
) : UserRepository
// Domain layer
@Factory
class GetUserUseCase(private val repository: UserRepository)
@Factory
class UpdateUserUseCase(private val repository: UserRepository)
// Presentation layer
@KoinViewModel
class UserListViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel()
@KoinViewModel
class UserDetailViewModel(
private val getUserUseCase: GetUserUseCase,
private val updateUserUseCase: UpdateUserUseCase
) : ViewModel()
// Module
@Module
@ComponentScan("com.myapp")
class AppModule
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
Next Steps
- Injection - Retrieve dependencies
- Scopes - Manage lifecycle
- DSL Reference - Complete DSL documentation
- Annotations Reference - Complete annotations documentation