KMM+MVVM架构下无法通过Kodein向Cookie管理器注入Context的问题求助
解决KMM中共享模块无法通过Kodein获取Android Context的问题
你遇到的org.kodein.di.DI$NotFoundException: No binding found for bind<Context>错误,核心原因是共享模块的useCaseModule对应的DI容器中没有包含Context的绑定。你在LoginUseCase构造函数中传入的di是useCaseModule内部的DI实例,这个模块仅导入了自身的依赖,并未关联到Android端包含Context绑定的appModule,因此CookiesStorageImpl尝试获取Context时自然找不到对应绑定。
下面提供两种解决方案,优先推荐第一种(符合KMM跨平台设计原则):
方案一:抽象跨平台存储接口,解耦Android依赖
这种方式让共享模块完全不依赖Android平台API,更贴合KMM的跨平台设计理念。
- 在共享模块定义抽象存储接口
// 共享模块 - 定义跨平台通用的键值存储抽象 interface KeyValueStorage { fun putString(key: String, value: String) fun getString(key: String): String? fun getAllKeys(): Set<String> }
- 重构共享模块的CookieStorage,依赖抽象存储
修改CookiesStorage,让它依赖抽象的KeyValueStorage,移除对平台特定Context的直接依赖:
// 共享模块 class CookiesStorage(private val storage: KeyValueStorage) : CookiesStorage { override suspend fun addCookie(requestUrl: Url, cookie: Cookie) { storage.putString(cookie.name, cookie.value) } override fun close() { } override suspend fun get(requestUrl: Url): List<Cookie> { return storage.getAllKeys().mapNotNull { key -> storage.getString(key)?.let { Cookie(key, it) } }.toMutableList() } }
- Android端实现抽象存储接口
// Android模块 class SharedPreferencesStorage(private val context: Context) : KeyValueStorage { private val prefs = context.getSharedPreferences("kmm_preferences", Context.MODE_PRIVATE) override fun putString(key: String, value: String) { prefs.edit().putString(key, value).apply() } override fun getString(key: String): String? { return prefs.getString(key, null) } override fun getAllKeys(): Set<String> { return prefs.all.keys } }
- 调整DI配置,注入抽象存储
- Android端DI:绑定
KeyValueStorage的Android实现
val appModule = DI.Module("app module") { bind<Context>() with multiton { app: App -> app.applicationContext } // 绑定跨平台存储的Android实现 bind<KeyValueStorage>() with singleton { SharedPreferencesStorage(instance()) } import(viewModelModule) }
- 共享模块DI:注入
CookiesStorage并重构LoginUseCase
val useCaseModule = DI.Module("use case module") { // 注入CookiesStorage,依赖抽象的KeyValueStorage bind<CookiesStorage>() with singleton { CookiesStorage(instance()) } // LoginUseCase不再传递DI,直接注入所需依赖 bind<LoginUseCase>() with singleton { LoginUseCase(instance()) } }
- 重构HttpClient,通过DI获取CookiesStorage
// 共享模块 private val httpClient = HttpClient { install(HttpCookies) { storage = instance<CookiesStorage>() } }
方案二:调整DI模块依赖,让共享模块访问Context(不推荐,耦合Android)
如果你暂时不想大规模重构代码,可以调整DI模块的依赖关系,让共享模块能访问到Android端的Context绑定,但这种方式会让共享模块依赖Android平台,违背KMM的跨平台初衷。
- 修改Android端DI的模块导入顺序
确保Context绑定先被加载,再导入其他模块:
val appModule = DI.Module("app module") { // 先绑定Context,再导入其他模块 bind<Context>() with multiton { app: App -> app.applicationContext } import(viewModelModule) }
- 传递全局DI实例给LoginUseCase
在viewModelModule中,传递全局DI而非模块内DI:
val viewModelModule = DI.Module("view model module") { import(useCaseModule) bind<ViewModelProvider.Factory>() with singleton { ViewModelFactory(instance()) } // 传递全局DI实例给LoginViewModel bind<LoginViewModel>() with provider { LoginViewModel(instance()) } } // 共享模块的useCaseModule val useCaseModule = DI.Module("use case module") { // 注入全局DI实例给LoginUseCase bind<LoginUseCase>() with singleton { LoginUseCase(instance<DI>()) } }
此时LoginUseCase拿到的是包含Context绑定的全局DI,CookiesStorageImpl就能正常获取Context了。
额外优化建议
- 避免在共享模块中直接依赖平台特定API(如Android的
Context),尽量采用抽象接口+平台实现的方式,保证跨平台兼容性。 - 不要在业务类(如UseCase)中直接传递DI容器,而是通过构造函数注入具体依赖,这样代码更易测试和维护。
内容的提问来源于stack exchange,提问作者Sergei Mikhailovskii
相关产品推荐
相关产品推荐

