Skip to main content
Version: 4.2

Injected Parameters

Some objects need a value that only the caller knows, such as a user ID or the current screen. Injected parameters let the caller pass these values when it asks Koin for an instance. This page covers how to declare a parameter in a definition, how to pass it, and how Koin matches values to parameters. It assumes you know how to declare a definition (see Definitions).

Declare and pass a parameter​

Mark which constructor parameters come from the caller. The other parameters are resolved from the container as usual:

import org.koin.core.annotation.InjectedParam

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

val appModule = module {
single<UserRepository>()
factory<UserPresenter>()
}

@InjectedParam comes from the koin-annotations artifact. It tells the Compiler Plugin that userId is not a definition, so the build-time check doesn't report it as missing.

The caller passes the values with parametersOf(), in the same order as the parameters:

import org.koin.core.parameter.parametersOf

val presenter: UserPresenter = get { parametersOf("user-42") }

// or lazily
val presenter: UserPresenter by inject { parametersOf("user-42") }

The parameter block is a lambda, so the values are read when the instance is created. With by inject(), this is on first access.

Injected parameters are meant for factory definitions. A single is created once: the values passed the first time are used, and later values are ignored.

Each platform API takes the same block: koinInject { parametersOf(userId) } in Compose (see Koin for Compose), and by viewModel { parametersOf(userId) } or koinViewModel { parametersOf(userId) } for a ViewModel (see ViewModel).

Several parameters​

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

val appModule = module {
factory<OrderPresenter>()
}
val presenter: OrderPresenter = get { parametersOf("user-42", 1001L) }

How Koin matches values​

params.get<T>() and the generated code look up each value with these rules:

  • Koin first looks at the value at the current position. If it has the requested type, Koin returns it and moves to the next position.
  • Otherwise, Koin returns the first value of the requested type, wherever it is.
  • params.getOrNull<T>() returns null when no value of that type was passed. get<T>() throws DefinitionParameterException.

In practice, pass the values in constructor order: parametersOf("user-42", 1001L) fills userId then orderId, and two String parameters receive two String values in order.

If you need one behavior only, build the parameters with another function:

FunctionMatching
parametersOf(...)Position first, then type (default, rules above)
parameterArrayOf(...)Position only. The value at the current position must have the requested type
parameterSetOf(...)Type only. Every get<T>() returns the first value of that type
val byIndex = parameterArrayOf(1, 2, 3)
byIndex.get<Int>() // 1
byIndex.get<Int>() // 2

val byType = parameterSetOf("a_string", 42)
byType.get<Int>() // 42
byType.get<Int>() // 42

Values resolved from parameters​

When Koin creates an instance with parameters, a plain get() inside the definition checks the passed values before the container. A parameter that is not marked can still receive a passed value:

val appModule = module {
factory { UserPresenter(userId = get(), repository = get()) }
}

get<UserPresenter> { parametersOf("user-42") } // userId = "user-42"

This also works for dependencies further down: if UserPresenter needs a Tracker that needs the screen name, a value passed for the presenter can reach the Tracker definition.

Prefer @InjectedParam or params.get() anyway: they say where the value comes from, and the Compiler Plugin can check the rest of the graph.

warning

If a passed value has the same type as the requested definition, Koin returns that value directly and never runs the definition. get<String> { parametersOf("x") } returns "x". Wrap such parameters in their own type (a value class, for example) when they would collide with the definition's type.

A qualified request, such as get(named("local")), never takes a value from the parameters. Parameters carry no qualifier.

  • Injection: get() and inject(), where you pass the parameters.
  • Definitions: how the other constructor parameters are resolved.
  • ViewModel: parameters and SavedStateHandle for ViewModels.
  • Qualifiers: choose between definitions instead of passing values.
  • Annotations Reference: @InjectedParam and other parameter annotations.