Skip to main content
Version: 4.2

Koin DSL

Thanks to the power of the Kotlin language, Koin provides a DSL to help you describe your application's dependency injection container. With its Kotlin DSL, Koin offers a smart functional API to prepare your dependency injection without annotation processing or code generation.

Overview

Koin's DSL has two main parts:

  • Application DSL - Configure the Koin container itself (logging, modules, properties)
  • Module DSL - Declare the components and their dependencies

Application DSL

A KoinApplication instance represents your configured Koin container. This lets you set up logging, load properties, and register modules.

Creating a KoinApplication

Choose between two approaches:

  • koinApplication { } - Creates a standalone KoinApplication instance
  • startKoin { } - Creates a KoinApplication and registers it in the GlobalContext
// Standalone instance (useful for testing or custom contexts)
val koinApp = koinApplication {
modules(myModule)
}

// Global instance (standard approach for applications)
startKoin {
logger()
modules(myModule)
}

Configuration Functions

Within koinApplication or startKoin, you can use:

  • logger() - Set the logging level and Logger implementation (default: EmptyLogger)
  • modules() - Load modules into the container (accepts list or vararg)
  • properties() - Load a HashMap of properties
  • fileProperties() - Load properties from a file
  • environmentProperties() - Load properties from OS environment variables
  • createEagerInstances() - Instantiate all definitions marked with createdAtStart
  • allowOverride(Boolean) - Enable/disable definition overriding (default: true since 3.1.0)

Global vs Local Context

The key difference between koinApplication and startKoin:

  • startKoin - Registers the container in GlobalContext, making it accessible via KoinComponent, by inject(), and other global APIs
  • koinApplication - Creates an isolated instance you control directly
// Global context - standard usage
startKoin {
logger()
modules(appModule)
}

// Later, anywhere in your app:
class MyClass : KoinComponent {
val service: Service by inject() // Uses GlobalContext
}
// Local context - advanced usage (testing, multi-context apps)
val customKoin = koinApplication {
modules(testModule)
}.koin

val service = customKoin.get<Service>() // Use specific instance

Starting Koin

A complete Koin setup example:

startKoin {
// Configure logging
logger(Level.INFO)

// Load properties
environmentProperties()

// Declare modules
modules(
networkModule,
databaseModule,
repositoryModule,
viewModelModule
)

// Create eager singletons
createEagerInstances()
}

Module DSL

A Koin module is a logical grouping of definitions. It describes what to create and how to wire dependencies.

Creating a Module

Use the module function:

val myModule = module {
// Your definitions here
}

Definition Keywords

Koin provides several keywords for declaring components:

Lambda-Based Definitions

  • single { } - Singleton (one instance shared across the app)
  • factory { } - Factory (new instance every time)
  • scoped { } - Scoped (one instance per scope lifecycle)
module {
single { DatabaseHelper() } // Singleton
factory { NetworkRequest() } // Factory
scoped { UserSession() } // Scoped
}

Autowire Definitions (Constructor DSL)

  • singleOf() - Singleton with constructor autowiring
  • factoryOf() - Factory with constructor autowiring
  • scopedOf() - Scoped with constructor autowiring
class MyService(val repository: MyRepository)

module {
singleOf(::MyRepository)
singleOf(::MyService) // Dependencies autowired automatically
}
info

Learn more about Autowire DSL in the Autowire DSL documentation

Resolution & Dependency Injection

Use get() to resolve dependencies within definitions:

class Controller(val service: Service)
class Service(val repository: Repository)

module {
single { Repository() }
single { Service(get()) } // Inject Repository
single { Controller(get()) } // Inject Service
}

Binding Types

Single Type Binding

By default, definitions are bound to their exact type:

interface UserRepository
class UserRepositoryImpl : UserRepository

module {
// Bound as UserRepository (preferred)
single<UserRepository> { UserRepositoryImpl() }

// Also valid using cast
single { UserRepositoryImpl() as UserRepository }
}

Additional Type Binding

Bind a definition to multiple types using bind:

interface Logger
interface DebugLogger

class ConsoleLogger : Logger, DebugLogger

module {
// Available as both ConsoleLogger and Logger
single { ConsoleLogger() } bind Logger::class

// Available as all three types
single { ConsoleLogger() } binds arrayOf(
Logger::class,
DebugLogger::class
)
}

Qualifiers (Named Definitions)

Use named() to distinguish between multiple definitions of the same type:

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

// Retrieve by qualifier
val localDb: Database by inject(named("local"))
val remoteDb: Database by inject(named("remote"))

Qualifiers can be:

  • Strings: named("qualifier")
  • Types: named<MyType>()
  • Enums: named(MyEnum.VALUE)

Injection Parameters

Pass runtime parameters to definitions:

class UserPresenter(val userId: String, val view: UserView)

module {
factory { (userId: String, view: UserView) ->
UserPresenter(userId, view)
}
}

// Provide parameters when resolving
val presenter: UserPresenter = get { parametersOf("user123", myView) }

Definition Options

Using withOptions

Apply multiple options to a definition:

module {
single { MyService(get()) } withOptions {
named("primary")
createdAtStart()
bind<ServiceInterface>()
}
}

Available options:

  • named() - Assign a qualifier
  • bind<Type>() - Bind to additional type
  • binds() - Bind to multiple types
  • createdAtStart() - Create eagerly at startup
  • override() - Allow override even if global override is disabled (4.2.0+)
  • onClose { } - Register cleanup callback

Eager Instantiation

Create instances immediately at startup:

module {
// Single definition
single(createdAtStart = true) { CacheManager() }

// Or via withOptions
single { CacheManager() } withOptions {
createdAtStart()
}
}

You can also mark an entire module:

module(createdAtStart = true) {
single { ServiceA() }
single { ServiceB() }
}

Override Control (4.2.0+)

Mark specific definitions as allowed to override:

startKoin {
allowOverride(false) // Strict mode
modules(productionModule, testModule)
}

val productionModule = module {
single<Service> { ProductionService() }
}

val testModule = module {
// Explicitly allowed to override
single<Service> { MockService() } withOptions {
override()
}
}

Scopes

Define logical grouping for scoped instances:

module {
scope<MyActivity> {
scoped { ActivityPresenter() }
}
}
info

Learn more in the Scopes documentation

Lifecycle Callbacks

Register cleanup logic with onClose:

module {
single {
DatabaseConnection()
} onClose { connection ->
connection?.close()
}
}

Complete Example

Putting it all together:

// Data layer
class ApiClient
class Database
class UserRepository(val api: ApiClient, val db: Database)

// Domain layer
class GetUserUseCase(val repository: UserRepository)

// Presentation layer
class UserViewModel(val useCase: GetUserUseCase)

// Module definitions
val dataModule = module {
single { ApiClient() }
single { Database() }
single { UserRepository(get(), get()) }
}

val domainModule = module {
factory { GetUserUseCase(get()) }
}

val presentationModule = module {
factory { UserViewModel(get()) }
}

// Start Koin
fun main() {
startKoin {
logger(Level.INFO)
modules(dataModule, domainModule, presentationModule)
}
}

Best Practices

  1. Organize by layers - Create separate modules for data, domain, and presentation layers
  2. Use autowire DSL - Prefer singleOf(::MyClass) over single { MyClass(get()) } for cleaner code
  3. Leverage qualifiers - Use named() when you need multiple implementations of the same type
  4. Explicit types - Use single<Interface> { Implementation() } for clarity
  5. Module composition - Use includes() to compose larger modules from smaller ones
info

For more details on modules, see Modules documentation

info

For more details on definitions, see Definitions documentation