Skip to content

Chore: migrate DI from Koin to Metro (RND-1987)#2961

Open
StylianosGakis wants to merge 57 commits into
eng/metro-nav3-pr0-kotlin-bumpfrom
eng/metro-nav3-pr1-koin-to-metro
Open

Chore: migrate DI from Koin to Metro (RND-1987)#2961
StylianosGakis wants to merge 57 commits into
eng/metro-nav3-pr0-kotlin-bumpfrom
eng/metro-nav3-pr1-koin-to-metro

Conversation

@StylianosGakis
Copy link
Copy Markdown
Member

Summary

Migrates the entire app's dependency injection from Koin to Metro (compiler-plugin DI). This is PR1 of a two-PR stack; PR2 will migrate Navigation2 → Navigation3 on top of this. Navigation is unchanged here.

Stacked on top of eng/metro-nav3-pr0-kotlin-bump (Kotlin 2.3.21 bump, a Metro prerequisite).

What changed

  • Two Metro graphs: AppGraph (:app, Android) and IosGraph (:shareddi, iOS), both @DependencyGraph(AppScope::class).
  • Bindings: leaf data/use-case impls use @Inject + @ContributesBinding(AppScope::class); singletons via @SingleIn(AppScope::class).
  • ViewModels: no-arg ViewModels via @ContributesIntoMap multibinding; parameterized ViewModels via @AssistedInject + @AssistedFactory keyed factories.
  • ProdOrDemoProviders: demo-mode providers rebuilt as Metro @Provides functions on public @ContributesTo(AppScope::class) provider interfaces.
  • Qualifiers: string-keyed Koin qualifiers replaced with typed @Qualifier annotations.
  • WorkManager: Android WorkerFactory now Metro-multibound.
  • Koin fully removed: all Koin modules and dependencies deleted.

Coordinated iOS change (ugglan/)

The iOS bootstrap entry point changes from initKoin(...)initDiGraph(...). The Swift side (ugglan/) must be updated in lockstep when this lands.

Test plan

  • ./gradlew ktlintCheck — green
  • ./gradlew :app:assembleDebug — green
  • ./gradlew testDebugUnitTest — green
  • ./gradlew :shareddi:compileKotlinIosSimulatorArm64 — green
  • Manual smoke: arg-consuming screens (assisted ViewModels), WorkManager-scheduled work, iOS app boot via Xcode

The deps were gated on configurationName == "implementation", which is
also the path pure-JVM (hedvig.jvm.library) modules take, so ~33 JVM
modules pulled the Compose-bearing metrox-viewmodel-compose artifact.
Gate on the Android Gradle plugin instead so only Android modules — the
ones that host ViewModels resolved through the factory — receive them.
Metro's compiler plugin rejects language version 2.2; the compiler was
already bumped to 2.3.21 but the convention plugins still pinned the
language/api version to 2.2.
…composition local

Adds the Metro @DependencyGraph (AppGraph extending ViewModelGraph), an
AppGraphInitializer Startup entry that builds the graph and stores it in
AppGraphHolder, and wires LocalMetroViewModelFactory into the Compose
tree via CompositionLocalProvider. Koin is untouched and coexists with
Metro throughout PR1.
Add @ContributesBinding/@SingleIn/@Inject to impl classes in data-addons,
data-changetier, data-cross-sell-after-claim-closed, and
data-cross-sell-after-flow. Koin module files are untouched (coexistence).
Lets @ContributesBinding/@Inject impls (and their helper interfaces) stay
`internal` while still merging into the :app graph. Metro then generates
top-level @provides returning only the bound type instead of @BINDS shorthands,
so the merge site never needs to reference the internal impl class directly.

Without this flag an internal contributed class is silently dropped and only
fails at full-graph compile time. Requires Metro >= 0.13.0 and Kotlin >= 2.3.20
(on 1.1.1 / 2.3.21). Verified by merging an internal cross-module binding into
:app:compileDebugKotlin.
…oose-tier/insurance-certificate/chip-id DI to Metro
…tly/notification-badge/tier-comparison DI to Metro
…e-model/language-migration/demo-mode/forever-ui DI to Metro
…ificate/travel-certificate/payout-account ViewModels to Metro
Wires the Android-only @DatabaseFile File binding for the Metro graph,
mirroring the Koin databaseChatAndroidModule. The @iodispatcher and
@BaseHttpClient providers live in commonMain (core-common, network-clients)
and are deferred to the KMP graph task; Qualifier.kt deletion is deferred to
Koin removal since Koin modules still reference the string qualifiers.
Introduces ChildWorkerFactory + @workerkey multibinding infra in
core-common-public/androidMain, converts FCMTokenUploadWorker to assisted
injection contributing into the worker map, and adds MetroWorkerFactory plus
an AppGraph accessor. The worker constructor keeps its signature so Koin's
worker DSL still builds it during coexistence; wiring the factory into the
Application's Configuration.Provider is deferred to Koin removal.
Add @ContributesBinding/@SingleIn/@Inject to interface-backed use-case
impls across 11 KMP modules so they merge into the Metro graph alongside
the existing Koin bindings during coexistence. Purely additive — Koin
modules untouched.
Add Metro multibinding annotations to the 10 commonMain ViewModels in
feature-help-center, feature-remove-addons and feature-claim-chat:
no-arg VMs get @Inject/@ViewModelKey/@ContributesIntoMap; parameterized
VMs get @AssistedInject with a nested ManualViewModelAssistedFactory.
Add metrox-viewmodel to each commonMain so the annotations resolve on
the iOS/native classpath too. Koin call sites untouched (coexistence).
Mirrors the existing Koin modules with @ContributesTo(AppScope) Metro
providers so both the Android AppGraph and the iOS IosGraph can resolve
ApplicationScope, IO dispatcher, the Ktor/Apollo network chain, and the
platform DataStore<Preferences>. Koin modules kept intact for coexistence.
Introduce IosGraph (Metro @DependencyGraph) for the iOS side, wired
through IosDiHolder in design-system-hedvig to avoid a circular
dependency with feature modules. Convert iOS-reachable ViewModel call
sites (help-center, claim-chat, remove-addons) to metroViewModel/
assistedMetroViewModel, and add native Metro bindings for FileService
and AudioRecordingManager. Koin still runs alongside Metro on iOS for
not-yet-flipped call sites.
Delete all Koin DI modules and the koin-bom auto-injection, removing the
koin dependencies from the version catalog. Resolve the resulting Metro
MissingBinding errors at the :app graph root by widening the four
demo/prod providers consumed there (paying-member, missed-payment,
chat repository, insurance contracts) to the data-addons idiom: public
provider interfaces, internal impls bound via @ContributesBinding, and
public demo classes.

Expose room.runtime and the AppDatabase/ChatDao-bearing modules via api
so Metro can resolve RoomDatabase.Builder<AppDatabase> when reading the
generated database factory from :app.
Mechanical line wraps to satisfy max-line-length (120) now enforced
by the bumped ktlint, plus annotation-spacing fixes from ktlintFormat.
@StylianosGakis StylianosGakis requested a review from a team as a code owner June 1, 2026 09:19
@panasetskaya panasetskaya changed the title refactor: migrate DI from Koin to Metro Chore: migrate DI from Koin to Metro (RND-1987) Jun 1, 2026
@notion-workspace
Copy link
Copy Markdown

StylianosGakis and others added 8 commits June 1, 2026 16:43
Replace the two process-global graph holders (AppGraphHolder and the
MetroGraphHolder object) with a single source of truth: the graph lives
on HedvigApplication. :app-internal callers read the typed `appGraph`;
feature modules reach it through the new MetroGraphProvider interface and
cast to their own @ContributesTo entry point, exactly as before.
Construct the prod and demo implementations inline inside the Metro
@provides factories instead of contributing them as bindings. Only the
factory needs the impls, so they (and their ProdOrDemoProviders) can drop
their DI annotations and become internal, keeping GraphQL/impl details out
of cross-module visibility.
Construct GetHomeDataUseCase prod/demo impls inline in the Metro @provides
factory and make them (plus the provider and SeenImportantMessagesStorageImpl)
internal, so the impls are no longer exposed across the module boundary.
Construct the prod and demo ContactInfoRepository impls inline in the Metro
@provides factory and make them and ProfileRepositoryProvider internal.
Construct the prod and demo ForeverRepository impls inline in the Metro
@provides factory and make them and ForeverRepositoryProvider internal.
Construct the prod and demo GetCrossSellSheetDataUseCase impls inline in the
Metro @provides factory and make them internal.
…-to-metro-start-crush-fix

Bugfix: bind MoleculeViewModels as ViewModel and complete Metro wiring
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants