From a4a82008954b0238d6a8d5337229fd378a528ee6 Mon Sep 17 00:00:00 2001 From: tezov Date: Thu, 15 Jan 2026 01:31:12 +0100 Subject: [PATCH 1/2] getAllOrdered feature request --- proposals/getAllOrdered.md | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 proposals/getAllOrdered.md diff --git a/proposals/getAllOrdered.md b/proposals/getAllOrdered.md new file mode 100644 index 0000000..fa101e6 --- /dev/null +++ b/proposals/getAllOrdered.md @@ -0,0 +1,170 @@ +| | | +|-------------|------------------------------------| +| Feature | GetAllOrdered | +| Submitted | 2026-01 | +| Status | Draft | +| Issue | | +| Project Card | https://github.com/by-tezov/tuucho | +| Project | [Koin] | +| Component | core | +| Version | 4.2.0+ | + +--- + +## Summary + +### What is this proposal about? What problem does it solve? + +allow to retrieve a list of bound declaration in the exact same order they were declared. the actual getAll does not keep order. + +--- + +## Motivation + +### Why is this change important for Koin? + +important for Koin, no idea, but for me, yes xD. + +In my library project [link](https://github.com/by-tezov/tuucho), I allow the user library to create middlewares which is injected in the Isolated Koin context to have control on the library behavior (Network, navigation, other stuff) + +Example: + +My library have navigation stack, the user can wrap the core navigation inside their own middleware to guard auth section and/or redirect on the fly, load configuration, catch errors etc. + +To keep the code clean, we declare different middleware + +``` +CatchErrorMiddleware [ + BeforeNavigateMiddleware [ + // do some stuff + CoreNavigationMiddleware [ + // core library code + AfterLoggerMiddleware [] + ] + ] + // catch contains all middlewares and is sure to react on unmanaged errors +] +``` + +this can work has expected only if the order of middleware is preserved (same concept as nestJS, or retrofit interceptor). + +--- + +## Proposed Solution + +### Explain the approach simply. Add a short example if helpful. + +Example of use : + +``` +module { + factoryOf(::CatcherBeforeNavigateToUrlMiddleware) bindOrdered NavigationMiddleware.ToUrl::class + + factory { + BeforeNavigateToUrlMiddleware() + } bindOrdered NavigationMiddleware.ToUrl::class + + factoryOf(::LoggerBeforeNavigateToUrlMiddleware) bindOrdered NavigationMiddleware.ToUrl::class +} +-> module is fed to koin isolated context (or koin) + +and somewhere in the Library + +factory { + NavigateToUrlUseCase( + coroutineScopes = get(), + useCaseExecutor = get(), + ... + middlewareExecutor = get(), + navigationMiddlewares = getAllOrdered() + ) +} +``` +full example can be seen in sample application shared module [link](https://github.com/by-tezov/tuucho) + + +And I implemented it like this : + +``` +@OptIn(KoinInternalApi::class) +@KoinDslMarker +infix fun KoinDefinition.bindOrdered( + clazz: KClass +): KoinDefinition { + val index = module.mappings.values + .distinctBy { it.beanDefinition } + .count { it.beanDefinition.secondaryTypes.contains(clazz) } + bind(clazz) + val typeName = clazz.qualifiedName ?: error("class qualified name is null") + val orderedQualifier = named("$typeName#ordered#$index") + val mapping = indexKey(clazz, orderedQualifier, factory.beanDefinition.scopeQualifier) + module.mappings[mapping] = factory + return this +} + +@OptIn(KoinInternalApi::class) +@KoinDslMarker +inline fun Koin.getAllOrdered(): List = scopeRegistry.rootScope.getAllOrdered(T::class) + +@KoinDslMarker +inline fun Scope.getAllOrdered(): List = getAllOrdered(T::class) + +@OptIn(KoinInternalApi::class) +@KoinDslMarker +fun Scope.getAllOrdered( + clazz: KClass +): List = with(getKoin()) { + val typeName = clazz.qualifiedName ?: error("class qualified name is null") + val instanceContext = ResolutionContext(logger, scopeRegistry.rootScope, clazz) + instanceContext.scopeArchetype = scopeRegistry.rootScope.scopeArchetype + instanceRegistry.instances.entries + .filter { (key, factory) -> + key.contains("$typeName#ordered#") && + ( + (factory.beanDefinition.scopeQualifier == instanceContext.scope.scopeQualifier || + factory.beanDefinition.scopeQualifier == instanceContext.scope.scopeArchetype + ) && + (factory.beanDefinition.primaryType == clazz || factory.beanDefinition.secondaryTypes.contains(clazz)) + ) + }.distinct() + .mapNotNull { (key, factory) -> + val index = key + .substringAfter("#ordered#", "") + .substringBefore(':') + .toIntOrNull() ?: return@mapNotNull null + index to factory + }.sortedBy { it.first } + .mapNotNull { + @Suppress("UNCHECKED_CAST") + it.second.get(instanceContext) as? T + } // TODO linked scope, can't do because it is internal +} +``` + +it works for me, but the getAllOrdered is ugly and use too much KoinInternalApi + +code here [link](https://github.com/by-tezov/tuucho/blob/master/tuucho/core-modules/domain/business/src/commonMain/kotlin/com/tezov/tuucho/core/domain/business/_system/koin/BindOrdered.kt) + +unit test here [link](https://github.com/by-tezov/tuucho/blob/master/tuucho/core-modules/domain/business/src/commonTest/kotlin/com/tezov/tuucho/core/domain/business/_system/koin/BindOrderedTest.kt) + +--- + +## Drawbacks & Alternatives + +### What are the trade-offs? Were other options considered? + +the way I did it works only if bindOrdered are done on the same module. Cross modules overwrite previous module that bind the same interface (can be seen in unit tests), not an issue in my context since library user can use a moduleContext (that's another topic, I will open another KFIP for that) + +--- + +## Implementation Notes (optional) + +### Anything relevant for contributors or maintainers to know? + +I mainly talk about Library project in my request, but I think in simple application, it could also be useful + +--- + +## Future Considerations (optional) + +### How might this evolve or inspire related features? From 01fd9db09f62f3decb6e6f22d6714eadd13b9b0b Mon Sep 17 00:00:00 2001 From: tezov Date: Thu, 15 Jan 2026 11:32:17 +0100 Subject: [PATCH 2/2] fix typo --- proposals/getAllOrdered.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/proposals/getAllOrdered.md b/proposals/getAllOrdered.md index fa101e6..87be1c5 100644 --- a/proposals/getAllOrdered.md +++ b/proposals/getAllOrdered.md @@ -23,15 +23,13 @@ allow to retrieve a list of bound declaration in the exact same order they were ### Why is this change important for Koin? -important for Koin, no idea, but for me, yes xD. - In my library project [link](https://github.com/by-tezov/tuucho), I allow the user library to create middlewares which is injected in the Isolated Koin context to have control on the library behavior (Network, navigation, other stuff) Example: -My library have navigation stack, the user can wrap the core navigation inside their own middleware to guard auth section and/or redirect on the fly, load configuration, catch errors etc. +My library have navigation stack, the user can wrap the core navigation inside their own middlewares to guard auth section and/or redirect on the fly, load configuration, catch errors etc. -To keep the code clean, we declare different middleware +To keep the code clean, we declare different middlewares ``` CatchErrorMiddleware [ @@ -46,7 +44,7 @@ CatchErrorMiddleware [ ] ``` -this can work has expected only if the order of middleware is preserved (same concept as nestJS, or retrofit interceptor). +this can work as expected only if the order of middlewares is preserved (same concept as nestJS, or retrofit interceptor). --- @@ -66,9 +64,9 @@ module { factoryOf(::LoggerBeforeNavigateToUrlMiddleware) bindOrdered NavigationMiddleware.ToUrl::class } --> module is fed to koin isolated context (or koin) +-> module is fed to koin -and somewhere in the Library +and somewhere else in the Library factory { NavigateToUrlUseCase( @@ -76,7 +74,7 @@ factory { useCaseExecutor = get(), ... middlewareExecutor = get(), - navigationMiddlewares = getAllOrdered() + navigationMiddlewares = getAllOrdered() <- here the retrieval happens ) } ``` @@ -141,11 +139,10 @@ fun Scope.getAllOrdered( } ``` -it works for me, but the getAllOrdered is ugly and use too much KoinInternalApi - -code here [link](https://github.com/by-tezov/tuucho/blob/master/tuucho/core-modules/domain/business/src/commonMain/kotlin/com/tezov/tuucho/core/domain/business/_system/koin/BindOrdered.kt) +it works for me, but the getAllOrdered is ugly and use too much KoinInternalApi. -unit test here [link](https://github.com/by-tezov/tuucho/blob/master/tuucho/core-modules/domain/business/src/commonTest/kotlin/com/tezov/tuucho/core/domain/business/_system/koin/BindOrderedTest.kt) +- code here [link](https://github.com/by-tezov/tuucho/blob/master/tuucho/core-modules/domain/business/src/commonMain/kotlin/com/tezov/tuucho/core/domain/business/_system/koin/BindOrdered.kt) +- unit test here [link](https://github.com/by-tezov/tuucho/blob/master/tuucho/core-modules/domain/business/src/commonTest/kotlin/com/tezov/tuucho/core/domain/business/_system/koin/BindOrderedTest.kt) ---