mcpField
K
koin

koin

github.com/rcosteira79开发165133 次安装6 周前更新
代码仓库

关于 koin

面向 Kotlin Android 与 KMP 开发者的 Koin 依赖注入参考技能,聚焦跨平台模块组织、Compose 与作用域等易错点,并说明依赖图验证和渐进式注解用法。

主要功能

  1. 01KMP source set 依赖组织
  2. 02KSP 注解模块生成
  3. 03Compose ViewModel 注入
  4. 04Activity 保留与自定义作用域
  5. 05verify() 依赖图检查

适用场景

  1. 01为 Android 与 iOS 配置平台依赖
  2. 02通过注解生成并注册模块
  3. 03管理配置变更期间的对象生命周期
  4. 04在测试中发现缺失依赖绑定

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rcosteira79/android-skills --skill koin

安装后在 Claude Code 中输入/koin确认已加载。

详细介绍

Koin 依赖注入(Android 和 KMP)

务实的 Kotlin DI。Koin 与 Hilt 的区别: Koin 可在 commonMain 中运行(Hilt 不行),并通过 verify() 验证依赖图,而不是使用代码生成;Hilt 仅支持 Android,采用代码生成并深度集成 Jetpack(@HiltViewModel / hiltViewModel())。本文介绍容易踩坑的地方和适用边界,不涵盖 Classic DSL 基础(single / factory / scoped / 使用 get() 解析和运行时参数的 viewModel { }、koinViewModel / koinInject、startKoin { androidLogger(); androidContext(this@MyApp); modules(...) })。相关内容: android-skills:kmp-ktor、android-skills:android-data-layer。依赖通过 koin-bom 管理:koin-core、koin-android、koin-androidx-compose、koin-compose-viewmodel(KMP)、koin-compose-viewmodel-navigation(Nav 3)、koin-test,以及可选的 koin-annotations / koin-ksp-compiler。

KSP 注解 — 生成的模块访问器

@Module @ComponentScan 会发现带注解的类,并生成一个传给 startKoin 的模块,即 UserModule().module —— 带注解类上生成的 .module 扩展是最容易遗漏的部分。每个模块选用一种风格(不要在同一个模块中混用 Classic DSL 和注解;不同模块之间可以切换)。

@Single class UserRepositoryImpl(private val service: UserService) : UserRepository
@Factory class UserFormValidator
@KoinViewModel class UserDetailViewModel(
    @InjectedParam private val userId: String,
    private val repository: UserRepository,
) : ViewModel()
@Module @ComponentScan("com.example.feature.user") class UserModule
// startKoin { modules(UserModule().module) }

KMP 源集布局

大多数绑定放在 commonMain 中;依赖平台类型的绑定(HTTP 引擎、Context、Keychain)应通过 expect val platformModule: Module 隔离,并在每个平台提供一个 actual(与 android-skills:kmp-ktor 中的引擎模式相同)。iOS 启动:从 iOSApp.init() 调用 InitKoinKt.doInitKoin(config: nil) —— Swift 将 init 作为保留名称,因此添加了 do 前缀。

// commonMain
expect val platformModule: Module
// androidMain: actual = module { single<HttpClientEngine> { OkHttp.create() }; single<TokenStorage> { DataStoreTokenStorage(get()) } }
// iosMain:     actual = module { single<HttpClientEngine> { Darwin.create() }; single<TokenStorage> { KeychainTokenStorage() } }

Compose:调用点故障

koinViewModel / koinInject 需要 koin-androidx-compose(Android)或 koin-compose-viewmodel(KMP)。缺少该依赖时,调用可以通过编译,但会在调用点失败 —— koin-core 并不了解 ViewModel 或 Compose 运行时。为便于测试,可组合函数的参数默认值使用 koinInject():fun Screen(service: AnalyticsService = koinInject())。

val vm: UserDetailViewModel = koinViewModel { parametersOf(userId) }
// keyed per entity: koinViewModel(key = "detail_$userId", parameters = { parametersOf(userId) })

作用域

跨配置更改保留(常见情况)→ activityRetainedScope —— 由 Activity 保留的 ViewModel 支撑,因此在屏幕旋转期间仍会保留,并在 Activity 真正结束时关闭。使用 activityRetainedScope { }(或 @ActivityRetainedScope)声明,并通过 AndroidScopeComponent 访问;这种情况下不要手动实现 createScope / close。

val checkoutModule = module {
    activityRetainedScope {                 // retained across config changes
        scoped { CheckoutCart() }
        scoped { CheckoutPricing(get()) }
    }
}
class CheckoutActivity : AppCompatActivity(), AndroidScopeComponent {
    override val scope: Scope by activityRetainedScope()
    private val cart: CheckoutCart by inject()
}

不与 Activity 绑定的自定义生命周期(例如以订单 ID 为键的多步骤流程)→ 声明一个 scope<T> 并管理其生命周期:getKoin().createScope<CheckoutFlow>("checkout-$orderId"),使用 scope.get() 解析,并在流程结束时调用 scope.close()。

Nav 3

koinEntryProvider() 会按目标位置解析 ViewModel;应在模块中注册目标位置,而不是直接写在 NavDisplay 位置。

val navigationModule = module {
    navigation<HomeRoute> { HomeScreen(viewModel = koinViewModel()) }
    navigation<DetailRoute> { route -> DetailScreen(viewModel = koinViewModel { parametersOf(route.id) }) }
}
NavDisplay(backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = koinEntryProvider())

测试 — verify() / checkModules()

遍历每个声明的构造函数,并确认所有依赖都已声明 —— 缺失的绑定会成为测试失败,而不是运行时的 NoDefinitionFoundException(弥补 Hilt 的一项优势)。在 CI 中运行。对于运行时解析的构造函数参数(例如 SavedStateHandle),请传入 extraTypes,否则 verify() 会误报。使用伪实现替换真实绑定(例如 Ktor MockEngine —— 参见 android-skills:kmp-ktor)。KoinTestRule(JUnit 4)/ KoinTestExtension(JUnit 5)会为每个测试安装上下文。

class ModuleVerificationTest : KoinTest {
    @Test fun `all modules resolve cleanly`() {
        koinApplication { modules(appModule, networkModule, platformModule) }.checkModules()
    }
    // Per-module with runtime-resolved types:
    // featureModule.verify(extraTypes = listOf(SavedStateHandle::class))
}

评论

支持 Markdown