You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的跨平台设计理念。

  1. 在共享模块定义抽象存储接口
// 共享模块 - 定义跨平台通用的键值存储抽象
interface KeyValueStorage {
    fun putString(key: String, value: String)
    fun getString(key: String): String?
    fun getAllKeys(): Set<String>
}
  1. 重构共享模块的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()
    }
}
  1. 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
    }
}
  1. 调整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的跨平台初衷。

  1. 修改Android端DI的模块导入顺序
    确保Context绑定先被加载,再导入其他模块:
val appModule = DI.Module("app module") {
    // 先绑定Context,再导入其他模块
    bind<Context>() with multiton { app: App -> app.applicationContext }
    import(viewModelModule)
}
  1. 传递全局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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 12:22:27