Retrieving Dependencies
This guide covers how to retrieve dependencies from Koin in different contexts.
Two Approaches
| Approach | When to Use | Example |
|---|---|---|
| Constructor Injection | Business logic, services, repositories | class MyService(val repo: Repository) |
| Field Injection | Android framework classes, entry points | val viewModel: MyVM by viewModel() |
Best Practice: Prefer constructor injection for better testability. Use field injection only when you don't control the class construction (Activities, Fragments, etc.).
Constructor Injection (Recommended)
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.
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
| Method | When Created | Thread Safety |
|---|---|---|
by inject() | On first access | Thread-safe lazy |
get() | Immediately | Direct 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())
}
}
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() {
get("/users") {
val repository: UserRepository = call.get()
call.respond(repository.getAll())
}
}
Injection with Qualifiers
When you have multiple definitions of the same type:
val module = module {
single<Database>(named("local")) { LocalDatabase() }
single<Database>(named("remote")) { RemoteDatabase() }
}
Inject by Name
// by inject()
private val localDb: Database by inject(named("local"))
private val remoteDb: Database by inject(named("remote"))
// get()
val localDb: Database = get(named("local"))
In Compose
@Composable
fun MyScreen() {
val localDb: Database = koinInject(named("local"))
}
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(), KoinComponent {
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
- Scopes - Manage dependency lifecycle
- Koin for Android - Android-specific injection
- Koin for Compose - Compose injection