Michael Paus
02/19/2026, 3:17 PMgalex
02/19/2026, 3:40 PMZac Sweers
02/19/2026, 3:43 PMjw
02/19/2026, 3:45 PMjw
02/19/2026, 3:46 PMTgo1014
02/19/2026, 3:48 PMjw
02/19/2026, 3:51 PMVidmantas Kerbelis
02/19/2026, 3:53 PMErnestas
02/19/2026, 4:23 PMjw
02/19/2026, 4:25 PMSebastian Schuberth
02/19/2026, 4:25 PMHere's the PR for the curious ones like me:Interesting code stats,
+565-255, so about twice the amount of code, it seems.Oliver.O
02/19/2026, 4:26 PMPearce Keesling
02/19/2026, 4:42 PMby inject pattern is service locator but something like constructor injection is more traditional DI. I'm not an expert though so 🧂Pearce Keesling
02/19/2026, 4:44 PMby inject is a nice escape hatch. That being said we prefer constructor injection for most code. I've been following metro with interest though, maybe someday we'll migrateOliver.O
02/19/2026, 4:58 PMjw
02/19/2026, 5:03 PMMichael Paus
02/19/2026, 8:06 PMzsmb
02/19/2026, 8:17 PMSaid PR looks like a misuse of Koin to me. What changes I see for the Metro implementation looks very alike to a Koin KSP implementationI'd be genuinely curious to hear which part you consider a misuse of Koin there. We had a couple hacky points of dynamically registering / fetching things from Koin, but most of it was just the usual Koin setup and then ViewModel injections.
> Here's the PR for the curious ones like me:
Interesting code stats,If you look at it in detail, I think a lot of the lines added are just imports and new injection-related annotations on ViewModels. The majority of actual new code in there was about setting up the graph class for each platform, which essentially replaced the previous per-platform modules with Koin. I'll highlight this though: Metro made me think about how to properly wire dependencies because it didn't allow my lazy runtime hacks anymore. Of course, further feedback is always welcome on our DI setup, if something's suboptimal in the Metro version now, I'd love to improve it., so about twice the amount of code, it seems.+565-255
Karen Frangulyan
02/19/2026, 8:34 PMby inject() or similar (usually forced at platform entry/boundary points) then most of your code will be IoC DI. The wiring part in koin seems to be a service locator (all those get() calls in modules), but that doesn’t seem to bring issues. Other platform boundaries force “service locator”-ish calls even for dagger/hilt; you call hiltViewModel() in composables just like koinViewModel() - state hoisting and making those default arguments in composable functions help in both cases. So at the end of the day personal preferences will matter on this topic.Zac Sweers
02/19/2026, 9:19 PMApplication.getInstance(), etc. Some of these support JSR-330-esque behaviors for convenience. They are sometimes nameless conventions in a codebase or framework. IntelliJ platform has getInstance() all over the place, when I worked at Flipboard we had a magic FlipboardManager.instance, etc etc. They arise naturally if you're not using an intentional DI system because we as programmers generally try to organize our code 🙂.
Their focus and value prop is on ease of use to developers. Application.getInstance(), by inject(), etc will always be easier reaches. The fact that they are runtime-only by default means there is no build costs and your builds are always faster! Your code may fail at runtime if you're missing a dependency but at least it won't quietly do the wrong thing or fail with some obscure NPE.
You don't really have to think about how that dependency got here, there's an implicit trust system. That trust system can work really well in a tight code base with serious alignment across the contributors to it. That is alignment that naturally degrades with codebase and team size, no matter how well intentioned. Jake mentioned this in a panel we did with JB a few months back - those large team graph explosions creep up on you when you're no longer seeing every PR that comes through.
But, if you're a backend team that can very quickly deploy a fix to prod? That runtime cost risk significantly lower, your clients can see your errors and gracefully degrade. If you're running on a beefy AWS instance, you aren't nearly as concerned about runtime reflection performance as an obfuscated mobile app running on a battery. And hey, sometimes we just generally agree that the developer-cost to doing it the IoC way is just too high to write code the way we want, and using a little bit of de-risked service locating unlocks powerful declarative code patterns (i.e. Compose UI and composition locals).
There are other factors worth considering, but in my experience they're somewhat secondary to the above concerns.
• Testing in isolation is often much harder and requires using the framework's test harness. Or, as is the case in frankly most SL shops I've heard of, you just write significantly less or no tests
• Reflection allows you to do powerful, dynamic replacement strategies or framework integrations. At the same time, it is slower at runtime and nigh-impossible to use safely with a code optimizer or obfuscator.
Category two automates retrieval of the graph. It's a runtime O(n) lookup of dependencies, fails at runtime, and you have to request the dependenciesZac Sweers
02/19/2026, 9:19 PMZac Sweers
02/19/2026, 9:20 PMZac Sweers
02/19/2026, 9:21 PMjw
02/19/2026, 9:33 PMZac Sweers
02/19/2026, 9:33 PMColton Idle
02/19/2026, 11:10 PMBen Morris-Rains
02/20/2026, 4:47 AMZac Sweers
02/20/2026, 5:02 AMZac Sweers
02/20/2026, 5:10 AMzsmb
02/20/2026, 7:47 AMWhen someone says "Koin is switching from KSP to become a compiler plugin, like Metro, I see less reasons to switch than before", it's a fundamental misunderstanding of what is happening at compile time and implies that they're different tools accomplishing the same thing.It is indeed easy to see these as being syntactically and functionally "very similar" as someone not deeply involved in the area.
[Validation of certain APIs' correctness] gets tossed around a lot but it's worth being specific that [in Koin] it's more like a linter. Metro, Dagger, Anvil, etc also do all this in the form of just usage checks, but they are fundamentally not the same thing as compile-time dependency injection. Koin's docs have explicitly said that it's planned in the future, but not what it currently does and it's important enough to be worth not conflating.I get pretty lost here in the second sentence. I have some idea of what usage checks mean in Metro and others. But I don't know what's not the same thing as compile-time DI. And then I'm also not sure what Koin's docs say is planned for the future - is it the same kind of usage checks?
David
02/20/2026, 7:53 AMzsmb
02/20/2026, 7:57 AMcreateGraph /`startKoin`
• A class that provides non-user types as dependencies, for example providing Json and HttpClient
◦ AppGraph with @Provides methods / AppModule with @Singleton methods
• Classes registered as dependencies at their declaration like MuseumRepository
◦ @Inject with @SingleIn / @Singleton
• Classes bound to interfaces at their declaration like KtorMuseumApi and InMemoryMuseumStorage
◦ @SingleIn with @ContributesBinding / @Singleton
• ViewModels annotated to receive dependencies
◦ @ContributesIntoMap and @ViewModelKey / @KoinViewModel
• ViewModels looked up in Compose code with a convenience function
◦ metroViewModel() / koinViewModel()
What I do clearly see as a difference though is that if I remove the provided HttpClient or the annotations from MuseumRepository, Metro fails the build as expected while Koin crashes at runtime 😱. Which actually confuses me greatly, because if I expected something from moving to annotations with Koin, it was certainly the compile-time safety aspect.Zac Sweers
02/20/2026, 8:04 AM@Provides function with two different scope annotations. Basically basic static analysis that checks that you didn't write anything obviously broken. In a compiler plugin for example, this is all the stuff that goes in FIR checkers.
By compile-time graph validation, I mean that it actually resolved all graph dependencies from roots to providers/classes, did full cycle checking and topological sorting, broke valid cycles, and validated that what you declared in source can be satisfied. Basically exactly the case you mention with HttpClient. Metro builds that graph at compile-time done the graph analysis and ensured that HttpClient is reachable from whatever downstream binding requested it.
koin's current compile-time validation is just making sure you wrote a valid DSL, but it doesn't actually (fully?) validate your dependency graph as far as I know. I don't have the blog post/doc handy but I remember seeing that full compile-time graph validation was planned for the future, but right now remains runtime.David
02/20/2026, 8:16 AMWhat I do clearly see as a difference though is that if I remove the providedThis runtime uncertainty is why we are looking into switching over to Metro. It has happened once that we almost shipped an app with a broken dependency, and also another time that it got merged to main w/o someone noticing.or the annotations fromHttpClient, Metro fails the build as expected while Koin crashes at runtime 😱MuseumRepository
Karen Frangulyan
02/20/2026, 8:27 AMMichael Paus
02/20/2026, 8:43 AMdorche
02/20/2026, 9:03 AMmetroViewModel - this is (or was, I haven't checked recently) one of the main problems why kotlin-inject wasn't adopted by smaller teams - they don't want to have to maintain ViewModelFactories manually and come up with their utility function to access a view model from Compose (for example). And probably for a good reason, library authors most likely will write better code and maintain it better.
It would be superb if Metro gets to be one of the first KMP DI libraries that sheds some more light on Kotlin viewmodel injection into SwiftUI. Many teams will prefer using KMP without CMP and being able to easily access a viewmodel in SwiftUI similar to how you would do it in Compose is huge imo. Even if it's just a section on the Metro docs with recipe files it would fill a huge gap imo.Oliver.O
02/20/2026, 10:21 AMDoesn't this imply that you have to re-compile your code for every configuration change?The configuration is Kotlin code. How would you change it without recompiling?
Michael Paus
02/20/2026, 10:33 AMOliver.O
02/20/2026, 10:37 AMjw
02/20/2026, 3:26 PMOleg Yukhnevich
02/20/2026, 3:44 PMOliver.O
02/20/2026, 4:01 PMjw
02/20/2026, 4:06 PMOliver.O
02/20/2026, 4:13 PMjw
02/20/2026, 4:14 PMOliver.O
02/20/2026, 4:17 PMjw
02/20/2026, 4:18 PMOliver.O
02/20/2026, 4:19 PMOleg Yukhnevich
02/20/2026, 5:35 PMBehavior that's based on classpath content and order is just a nightmare for deterministic behavior and testing.Agree, but what if we can build on the generic idea of implicit service loading, rather than just the Java implementation? For example, if we have only static build-time service discovery, then when you compile your application, the compiler already knows all the providers for the main dispatcher or TZDB that will be used. So that the compiler can suggest removing some dependency (or exclude via some compiler option), or even force you to use those "5 lines of explicit code" to resolve ordering or to disambiguate. Yes, it will not be a full ServiceLoader like in Java, but it will cover many use cases, right? Will it somehow change your perspective about "Never use service loaders"?
jw
02/20/2026, 5:40 PMAndroidMainDispatcher or SystemTzdb in a single location and then have that instance propagated explicitly (or via a dependency injector) to everywhere that needs it.Colton Idle
02/21/2026, 3:30 PMjw
02/21/2026, 3:36 PMjw
02/21/2026, 3:36 PMjw
02/21/2026, 3:37 PMKaren Frangulyan
02/21/2026, 3:46 PMColton Idle
02/21/2026, 3:47 PMjw
02/21/2026, 3:56 PMDidier Villevalois
02/23/2026, 10:33 AM@Service Plugin interface and an @ServiceProvider -annotated implementation of this in each of our plugin modules. That interface defines two members: a val dependencies: List<Plugin> and a fun DI.Module.contribute() . The first allows us to do a topological sort of the plugins to contribute to the DI in the right order and the second allows to contribute to the DI.
I believe I could completely replace both sweet-spi and Kodein with Metro, using the module aggregation, am I right?
I am also interested in how I could have plugins discovered at runtime in platforms other than JVM/Android. Is this even possible? (I know this is a bit far from the original OP's question, but the discussion seems to have deviated to these subjects.)
(BTW, is there a Metro channel on slack? I've seen the GitHub discussions are enabled, but maybe this is too formal for my questions...)Oleg Yukhnevich
02/24/2026, 1:38 PMI am also interested in how I could have plugins discovered at runtime in platforms other than JVM/Android. Is this even possible?Yes, it's somehow possible. But, it's not really straightforward to do, as it will involve working with C ABI (for Native), JS ABI (for js/wasm-js), and WIT (for wasm, in the future, when Wasm Component Model will be supported by Kotlin). If you are interested, I can share (here, in DMs, or in a blog post) some POCs I've done based on my work on sweet-spi. kodee floating
Didier Villevalois
02/25/2026, 6:12 AMYes, it's somehow possible. But, it's not really straightforward to do [...]
Yeah, after thinking a bit more, I realized I would need to open the library, lookup for my entry point symbol, reinterpret it as a function and execute it... I would be glad for any POC you can share on this! 😁
zsmb
04/01/2026, 7:20 PMzsmb
04/01/2026, 7:40 PM@Singleton
class A(val b: B)
@Singleton
class B(val a: A)David
04/01/2026, 9:15 PMDidier Villevalois
04/01/2026, 9:54 PMZac Sweers
04/01/2026, 10:17 PMDidier Villevalois
04/02/2026, 5:30 AMbut generally disagree you need any runtime validation.
Not all applications are fully static in terms of features. Some also require some form of extensibility after being built. And that may happen through dynamic linking of new artifacts providing new features. (I mean imagine we had to statically build a single variant of the linux kernel, so that it contains all that is needed by all its users. Thank god, the kernel has a module system and a dynamic version of it...)
Metro's primary value prop is that it's 100% compile-time validation, including full graph validation (not just reachability)
Don't get me wrong, I love compile-time validation. If Metro had a way to validate both the different connected components (each artifact individually) and then provide a quick check at runtime that the available components fit and nothing is missing, then I would use it. Unfortunately, Metro doesn't cover all the DI use cases, but only the ones where you can account for all the modules of a product beforehand. Again, I would love to have more build-time check, but unfortunately Metro only covers the case of fully statically linked applications.
Zac Sweers
04/02/2026, 2:41 PMDidier Villevalois
04/02/2026, 2:45 PMDidier Villevalois
04/03/2026, 2:57 PM