koin
koin copied to clipboard
Multibinding like in Dagger 2
Dagger 2 has a good feature "Multibinding". It allows injection of multiple dependencies into collection. It's useful to make access by a key in a map of objects to make pluggable architecture.
Could you give some specs or proposal? Where? DSL | API ? a pseudo example
thanks 👍
module {
intoMap<KClass<Item>, ItemFactory>(ClassMapKey(Item1::class)) {
Item1Factory()
}
intoMap<KClass<Item>, ItemFactory>(ClassMapKey(Item2::class)) {
Item2Factory()
}
}
class Sample: KoinComponent {
val itemFactories: Map<MapKey<KClass<Item>, ItemFactory> by injectMultiMap()
}
interface MapKey<E> {
val key: E
}
class <E : Enum<E>> EnumMapKey(override val key : E) : MapKey<E>
class <E : Any> ClassMapKey(val value: KClass<E>) : MapKey<Class<E>>
Why do you add the MapKey type here? I think we could just use a generic K as the key.
@IVIanuu you mean something like this?
module {
intoMap<KClass<Item>, ItemFactory>(Item1::class) {
Item1Factory()
}
intoMap<KClass<Item>, ItemFactory>(Item2::class) {
Item2Factory()
}
}
class Sample: KoinComponent {
val itemFactories: Map<MapKey<KClass<Item>, ItemFactory> by injectMultiMap()
}
One of the possible variants it to use the qualifier as the key. But I am not sure about that option
If I wanted to multibind aSet
class Foo(value: Int)
// equivalent to:
// setOf(
// Foo(1), Foo(2)
// )
val myFirstSet: Set<Foo> by inject(named("first"))
// equivalent to:
// setOf(
// Foo(3), Foo(4)
// )
val mySecondSet: Set<Foo> by inject(named("second"))
would that be declared as
module {
intoSet(named("first")) { Foo(1) }
intoSet(named("first")) { Foo(2) }
intoSet(named("second")) { Foo(3) }
intoSet(named("second")) { Foo(4) }
}
And then for a map
class Foo(value: Int)
class Bar(value: Int)
// equivalent to:
// mapOf(
// "one" to Foo(1),
// "two" to Foo(2)
// )
val myMap: Map<String, Foo> by inject(named("first"))
// equivalent to:
// mapOf(
// Bar(2) to Foo(3),
// Bar(3) to Foo(4)
// )
val myOtherMap: Map<Bar, Foo> by inject(named("second"))
could I get away with a declaration like this -
module {
intoMap(key = "one", qualifier = named("first")) { Foo(1) }
intoMap(key = "two", qualifier = named("first")) { Foo(2) }
intoMap(key = "three") { Foo(101) } // unqualified, so not injected into 'myMap'
intoMap(key = Bar(2), qualifier = named("second")) { Foo(3) }
intoMap(key = Bar(3), qualifier = named("second")) { Foo(4) }
intoMap(key = Bar(7)) { Foo(99) } // unqualified, so not injected into 'myOtherMap'
}
Without using named parameters or qualifiers
module {
intoMap(Bar(4), named("second")) { Foo(5) } // without using named parameters
intoMap("three") { Foo(6) } // technically possible, but harder to read
intoMap(Bar(5)) { Foo(7) } // technically possible, but harder to read
}
That seems to hang together if I declare
inline fun <reified T> Module.intoSet(
qualifier: Qualifier? = null,
noinline definition: Definition<T>
) { ... }
inline fun <reified T> Module.intoMap(
key: Any,
qualifier: Qualifier? = null,
noinline definition: Definition<T>
) { ... }
You can make a proposal via PR. The problem of such, is the reuse of strings naming 🤔 that can lead to configuration error (misspelling)
Is there still any interest in this? It seems to have been a while, with no PR made :(
Imo this would be a great feature in line with koin's dynamic approach to loading and unload modules
This seems to work fairly well for anyone that stumbles upon this
fun <T> Module.set(qualifier: Qualifier) = single(qualifier) {
mutableSetOf<T>()
}
fun <T> Scope.getSet(qualifier: Qualifier) = get<MutableSet<T>>(qualifier)
fun <T> Module.intoSet(
setQualifier: Qualifier, valueQualifier: Qualifier, definition: Definition<T>
) {
single(valueQualifier, true) {
getSet<T>(setQualifier).add(definition(this, parametersOf()))
definition
}
}
what's the interest here? Inject into a set/map, some values?
In my case, I'm interested in binding multiple implementations to some type, which can then be injected together in a collection in order to contribute functionality based on whether a module is loaded or not.
Same here with cholwell. The goal is to inject multiple different implementations using a set/map, and use them as necessary by the program. For e.g, each implementation can have a different required condition to run, and the correct implementation can be selected at runtime, instead for hard coding the maximum number of implementation we can use in the constructor
Hello! You can find the example of injecting to a map with loading/unloading modules here.
Hi @qwert2603, unless I've misunderstood your article we're actually looking for a slightly more general case here. We need to be able to use definitions for the types contained in the multi binder in order to inject into them.
This would also be an interesting use-case for Koin-annotations. With Dagger and Anvil, the qualifier is set on the injected type rather than the module having to list all the qualifier/value pairs. See here for an example. (Note that while @Named is used there, any qualifier can be used, so no need to rely on arbitrary strings).
what's the interest here? Inject into a set/map, some values?
Interested in this for slightly different use case than what's been mentioned so far:
I want each Koin module to provide kotlinx.serialization rules for its models via a SerializersModule
Then, I want a top-level Koin module to get all SerializersModules from the graph and build a single Json instance that combines the rules of all downstream Koin modules
I love this suggestion, something I've had to do was to create multiple factories of SerializersModules with a named qualifier for each of them. Then when I need to build an instance of Json I use the getAll() as demonstrated below, would be nice to get some sort of standardised api for this though 💯
factory {
val json = Json {
ignoreUnknownKeys = true
isLenient = true
coerceInputValues = true
}
getAll<SerializersModule>().forEach {
json.serializersModule.plus(it)
}
return@factory json
}
what's the interest here? Inject into a set/map, some values?
Interested in this for slightly different use case than what's been mentioned so far:
I want each Koin module to provide
kotlinx.serializationrules for its models via aSerializersModuleThen, I want a top-level Koin module to get all
SerializersModules from the graph and build a singleJsoninstance that combines the rules of all downstream Koin modules
If I get the case here, the idea is to declare for example a JsonSerialiser in each module. In the main (o another) module, gather those serializers into Json config.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Seems a shame for this to die of staleness when there's a fair bit of interest 😁
Please add this feature 🙏
Do you use Koin Property? it's a way to add field and you can inject them later if you need: https://insert-koin.io/docs/reference/koin-core/start-koin#read-property-from-a-module
Seems like Injecting a list of dependencies should do. Here the question I have is will this be aggregated over all other modules?
you mean aggregating values along the module declaration?
interface LoggerDataSource
@Single
@Named("InMemoryLogger")
class LoggerInMemoryDataSource : LoggerDataSource
@Single
@Named("DatabaseLogger")
class LoggerLocalDataSource(private val logDao: LogDao) : LoggerDataSource
Let's take the example from docs. Say logging is being kept in it's own module, only LoggerDataSource was make public and all implementations were made internal. Now LoggerDataSource is being injected in another module (say :data). Although it's implementations are private to module will koin pick up and provide proper impl to :data
I maybe mistaken @mslalith but if I understand your question what you want to know is what Named qualifier of LoggerDataSource impl (LoggerInMemoryDataSource or LoggerInMemoryDataSource) if you simply define LoggerDataSource as an input e.g.
class DataRepository(logger: LoggerDataSource) {
// .....
}
Theoretically should result in some sort of runtime error as you've not defined a qualifier to inject possible with @Named e.g. class DataRepository(@Named("InMemoryLogger") logger: LoggerDataSource) with Annotations otherwise DataRepostitory(get(named("InMemoryLogger"))) with DSL
The only case this would possible differ is if you have another logger that is not bound to a named qualifier e.g.
@Single
class LoggerLocalDataSource(private val source: FileSystemManager) : LoggerDataSource
@Single
@Named("InMemoryLogger")
class LoggerInMemoryDataSource : LoggerDataSource
@Single
@Named("DatabaseLogger")
class LoggerLocalDataSource(private val logDao: LogDao) : LoggerDataSource
In the case of class DataRepository(logger: LoggerDataSource) the impl type that would possibly match this would be LoggerLocalDataSource
P.S I would wait for @arnaudgiuliani to confirm the above just to be sure, but you could also test this too 😃
Yes I would say that by default a qualifier also cover default type is there is no other definition for this type