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

TypeLifecycleUse Case
SingleOne instance for app lifetimeServices, repositories, databases
FactoryNew instance each timePresenters, use cases, stateful objects
ScopedOne instance per scopeActivity-bound, session-bound objects
ViewModelAndroid 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

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

  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

Next Steps