Skip to main content
Version: 4.2

Retrieving Dependencies

This guide covers how to retrieve dependencies from Koin in different contexts.

Approaches

ApproachWhen to UseExample
Constructor InjectionBusiness logic, services, repositoriesclass MyService(val repo: Repository)
Function InjectionFactory functions, buildersfun createHttpClient(val ds: DataSource): HttpClient
Field InjectionAndroid framework classes, entry pointsval viewModel: MyViewModel by viewModel()
info

Best Practice: Prefer constructor or function injection for better testability. Use field injection only when you don't control the class construction (Activities, Fragments, etc.).

Dependencies are declared in the constructor and resolved by Koin:

class UserRepository(
private val database: Database,
private val apiClient: ApiClient
)

class UserViewModel(
private val repository: UserRepository
) : ViewModel()
val appModule = module {
single<Database>()
single<ApiClient>()
single<UserRepository>()
viewModel<UserViewModel>()
}

Koin automatically resolves all constructor parameters.

Function Injection

Use functions to create instances when you need custom creation logic:

Compiler Plugin DSL

fun createHttpClient(dataSource: DataSource): HttpClient {
return HttpClient {
install(ContentNegotiation) { json() }
defaultRequest { url(dataSource.baseUrl) }
}
}

val appModule = module {
single<DataSource>()
single { create(::createHttpClient) }
}

Annotations

@Module
class NetworkModule {

@Singleton
fun createHttpClient(dataSource: DataSource): HttpClient {
return HttpClient {
install(ContentNegotiation) { json() }
defaultRequest { url(dataSource.baseUrl) }
}
}
}

Function injection is useful when:

  • Creating instances from external libraries you don't control
  • Complex initialization logic is needed
  • You need to configure builders or DSLs

Field Injection

Lazy Injection with by inject()

Creates the instance when first accessed:

class MyActivity : AppCompatActivity() {
// Lazy - created on first access
private val viewModel: UserViewModel by viewModel()
private val service: MyService by inject()
}

Eager Injection with get()

Creates the instance immediately:

class MyActivity : AppCompatActivity() {
// Eager - created immediately
private val service: MyService = get()
}

Comparison

MethodWhen CreatedThread Safety
by inject()On first accessThread-safe lazy
get()ImmediatelyDirect call

KoinComponent

For classes that need to inject dependencies but aren't Android components:

class MyHelper : KoinComponent {
private val service: MyService by inject()
private val database: Database = get()

fun doSomething() {
service.process(database.query())
}
}
warning

Avoid using KoinComponent in business logic classes. It creates tight coupling to Koin. Prefer constructor injection instead.

Platform-Specific Injection

Android

Activities and Fragments have built-in support:

class MainActivity : AppCompatActivity() {
// ViewModel injection
private val viewModel: UserViewModel by viewModel()

// Regular injection
private val analytics: AnalyticsService by inject()
}

class UserFragment : Fragment() {
// Fragment's own ViewModel
private val viewModel: UserViewModel by viewModel()

// Shared with Activity
private val sharedVM: SharedViewModel by activityViewModels()
}

Compose

@Composable
fun UserScreen() {
// Inject ViewModel
val viewModel: UserViewModel = koinViewModel()

// Inject any dependency
val analytics: AnalyticsService = koinInject()

// Activity-scoped ViewModel
val sharedVM: SharedViewModel = koinActivityViewModel()
}

Ktor

fun Route.userRoutes() {
val repository: UserRepository by inject()

get("/users") {
call.respond(repository.getAll())
}
}

Injection with Qualifiers

When you have multiple definitions of the same type, use qualifiers to distinguish them.

String Qualifier

DSLAnnotation
named("local")@Named("local")
// DSL
val module = module {
single<Database>(named("local")) { LocalDatabase() }
single<Database>(named("remote")) { RemoteDatabase() }
}

// Injection
private val localDb: Database by inject(named("local"))
private val remoteDb: Database by inject(named("remote"))
// Annotations
@Singleton
@Named("local")
class LocalDatabase : Database

@Singleton
@Named("remote")
class RemoteDatabase : Database

Type Qualifier

Use a type (class, object, or enum) as a qualifier for compile-time safety:

DSLAnnotation
named<LocalDb>()@Qualifier(LocalDb::class)
// Define qualifier types
object LocalDb
object RemoteDb

// DSL
val module = module {
single<Database>(named<LocalDb>()) { LocalDatabase() }
single<Database>(named<RemoteDb>()) { RemoteDatabase() }
}

// Injection
private val localDb: Database by inject(named<LocalDb>())
private val remoteDb: Database by inject(named<RemoteDb>())
// Annotations
@Singleton
@Qualifier(LocalDb::class)
class LocalDatabase : Database

@Singleton
@Qualifier(RemoteDb::class)
class RemoteDatabase : Database

In Compose

@Composable
fun MyScreen() {
// With string qualifier
val localDb: Database = koinInject(named("local"))

// With type qualifier
val remoteDb: Database = koinInject(named<RemoteDb>())
}

Injection with Parameters

Pass parameters at injection time:

Definition

@Factory
class UserPresenter(
@InjectedParam val userId: String,
val repository: UserRepository
)

// Or with DSL
factory<UserPresenter>()

Injection

// by inject()
private val presenter: UserPresenter by inject { parametersOf("user123") }

// get()
val presenter: UserPresenter = get { parametersOf("user123") }

In Compose

@Composable
fun UserScreen(userId: String) {
val presenter: UserPresenter = koinInject { parametersOf(userId) }
}

Multiple Parameters

@Factory
class OrderPresenter(
@InjectedParam val userId: String,
@InjectedParam val orderId: String,
val repository: OrderRepository
)

val presenter = get<OrderPresenter> { parametersOf("user123", "order456") }

Direct Koin Access

Access the Koin instance directly when needed:

// From GlobalContext
val koin = KoinPlatform.getKoin()
val service: MyService = koin.get()

// In KoinComponent
class MyClass : KoinComponent {
fun doSomething() {
val service: MyService = getKoin().get()
}
}

Nullable Injection

For optional dependencies:

// Returns null if not found
val optional: MyService? = getKoinOrNull()?.getOrNull()

// In KoinComponent
class MyClass : KoinComponent {
private val optional: MyService? = getOrNull()
}

Injection in Different Contexts

In ViewModel

class UserViewModel(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Constructor injection - no KoinComponent needed
}

In Service

class MyService : Service() {
private val repository: UserRepository by inject()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
repository.doSomething()
return START_STICKY
}
}

In BroadcastReceiver

class MyReceiver : BroadcastReceiver(), KoinComponent {
private val service: NotificationService by inject()

override fun onReceive(context: Context, intent: Intent) {
service.handleNotification(intent)
}
}

In WorkManager Worker

class MyWorker(
context: Context,
params: WorkerParameters,
private val repository: UserRepository // Injected by Koin
) : CoroutineWorker(context, params) {

override suspend fun doWork(): Result {
repository.syncData()
return Result.success()
}
}

// Module
val workerModule = module {
worker<MyWorker>()
}

Best Practices

DO: Constructor Injection for Business Logic

// Good - testable without Koin
class UserService(
private val repository: UserRepository,
private val validator: UserValidator
) {
fun createUser(data: UserData) = validator.validate(data).let {
repository.save(it)
}
}

// Test without Koin
@Test
fun testCreateUser() {
val mockRepo = mockk<UserRepository>()
val mockValidator = mockk<UserValidator>()
val service = UserService(mockRepo, mockValidator)
// Test directly
}

DO: Field Injection for Framework Classes

// Good - Activity construction is controlled by Android
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModel()
}

DON'T: KoinComponent in Business Logic

// Bad - tight coupling to Koin
class UserService : KoinComponent {
private val repository: UserRepository by inject()
}

// Good - constructor injection
class UserService(private val repository: UserRepository)

DON'T: Get in Constructors

// Bad - side effects in constructor
class MyService(
private val repo: UserRepository = get() // Don't do this!
)

// Good - let Koin inject
class MyService(private val repo: UserRepository)

Next Steps