Kotlin MPP 期望/实际具有不同的签名

Mau*_*ycy 11 kotlin kotlin-multiplatform

我有一个管理器类,它有一个 Android 和 iOS impl(来自第 3 方库)。“例如 MyManagerImpl()”。为了构建第 3 方管理器,iOS 不需要 Context,但 Android 需要。我创建了一个公共类“MyManager”,它提取了需要在 commonMain 中调用的所有公共方法。

//commonMain
expect class MyManager {
  fun method1()
  companion object Factory {
    makeManager(): MyManager
  }
}

val manager = MyManager.Factory.makeManager() // ex intended usage

//androidMain
MyManagerImpl(context: Context) {
  fun method1()
}

actual class MyManager private constructor(manager: MyManagerImpl) {
  ..
  actual companion object Factory {
    override fun makeManager(): MyManager {
      return MyManager(MyManagerImpl(?how to get context?))
    }
  }
}

//iosMain
MyManagerImpl() {
  fun method1()
}

actual class MyManager private constructor(manager: MyManagerImpl) {
  ..
  actual companion object Factory {
    override fun makeManager(): MyManager {
      return MyManager(MyManagerImpl())
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

合并两个实现的最干净的方法是什么?即使它们具有不同的构造函数依赖项,是否可以这样做?我们希望能够在 commonMain 中惰性地构造类​​。这可能吗?

Ram*_*man 7

上下文的依赖注入

我对 SQLDelight 也有同样的问题SqlDriver,它需要 Android 上的上下文,但在 iOS 上不需要。使用 Kodein-DI 或 Koin,可以通过使用上下文注入来完成此操作,而无需任何混乱的静态变量。

基本概念是expect/actual用于创建特定于平台的工厂类(ManagerFactory)。

在Android上,actual实现ManagerFactory将上下文作为参数,可以从DI上下文中获取(对于Android上的Kodein-DI,请参阅代码androidXModule 文档

一旦在 android 和 iOS DI 模块中定义了工厂类,就可以在公共模块中注入/检索它,并将检索到的实例MyManager绑定到 DI 中,并在需要的地方使用。

使用 Kodein-DI 看起来像这样:

共同主

//commonMain
expect class ManagerFactory {
    fun createManager(): MyManager
}

val sharedModule = DI.Module("common") {
    bind<MyManager>() with singleton { instance<ManagerFactory>().createManager() }

    // now you can inject MyManager wherever...
}
Run Code Online (Sandbox Code Playgroud)

android主程序

//androidMain
actual class ManagerFactory(private val context: Context) {
    actual fun createManager(): MyManager = MyAndroidManagerImpl(context)
}

val androidModule = DI.Module("android") {
    importAll(sharedModule)

    // instance<Context> via androidXModule, see `MainApplication` below
    bind<ManagerFactory>() with singleton { ManagerFactory(instance<Context>()) }
}

class MainApplication : Application(), DIAware {
    override val di by DI.lazy {
        // androidXModule (part of Kodein's android support library) gives us access to the context, as well as a lot of other Android services
        // see https://github.com/Kodein-Framework/Kodein-DI/blob/7.1/framework/android/kodein-di-framework-android-core/src/main/java/org/kodein/di/android/module.kt
        import(androidXModule(this@MainApplication))
        importAll(androidModule)
    }
}
Run Code Online (Sandbox Code Playgroud)

ios主程序

//iosMain
actual class ManagerFactory {
    actual fun createManager(): MyManager = MyNativeManagerImpl()
}

val iosModule = DI.Module("ios") {
    importAll(sharedModule)
    bind<ManagerFactory>() with singleton { ManagerFactory() }
}
Run Code Online (Sandbox Code Playgroud)


Kev*_*gan 2

一般来说,没有一种超级干净的方法可以做到这一点。没有办法只Context从 Android 全局获取 a。虽然不漂亮,但我会做这样的事情:

//androidMain
class MyManagerImpl(context: Context) {
    fun method1(){}
}

actual class MyManager private constructor(manager: MyManagerImpl) {

    actual companion object Factory {
        lateinit var factoryContxet :Context
        override fun makeManager(): MyManager {
            return MyManager(MyManagerImpl(factoryContxet))
        }
    }
}

class SampleApplication : Application{
    override fun onCreate() {
        super.onCreate()
        MyManager.Factory.factoryContxet = this
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您希望能够从任何代码调用此函数,请Context在应用程序启动时进行初始化。将其保留在静态参考中不会出现在每个人的最佳实践列表中,但这并不是一个技术问题。或者,您可以使用 执行类似的操作Activity,但这有它自己的一系列问题。

  • 感谢您的答复!由于我的 mpp 项目包含一个 android 库目标,我也许能够在 androidMain 中定义应用程序类来实现这种混乱,而客户端只需实现该应用程序类。我还认为使用 kodein 进行 DI 可能会清理一些东西,但我无法弄清楚如何检索伴随对象中的上下文。我会等着看是否有其他可能的解决方案出现,如果没有,我会接受您的答案,因为它确实解决了我的问题。再次感谢。 (2认同)