前言
在使用 KMP进行开发时,我发现了一个令人头疼的事情。
在使用Android平台特性时,我们往往需要获取 Context 示例,与 iOS 的服务不同,Context 的实例并不是能到处能获取到的,这造成了编写平台特性的 API非常痛苦。
以下以一个简单的按键震动例子带大家优雅地调用平台 API。
原生API
以下先介绍下原生的震动 API 该怎么使用
- Android
// androidMain fun vibrate(context: Context) { context.getSystemService(Vibrator::class.java)?.vibrate( VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) ) }
- iOS
// iOSMain fun vibrate() { UIImpactFeedbackGenerator( UIImpactFeedbackStyle.UIImpactFeedbackStyleMedium ).impactOccurred() }
它们最大的区别是,Android 需要 Context
上下文,由于这一个参数的存在,我们无法在 common模块定义一个这样的 expect
函数。
// commonMain
expect fun vibrate() // we can't do this...
如何破局?
我们可以把 Context
上下文理解成一个提供震动的服务,以这种思维来思考的话,我们很容易能想到依赖注入,我们不关心服务从哪来,只需要外部注入进来供内部使用即可。
// androidMain
class Vibrator(private val context: Context) {
fun vibrate() {
context.getSystemService(Vibrator::class.java)?.vibrate(
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
)
}
}
自此我们得到了一个震动工具,同理也可以在 iOS 端声明,由于 iOS 不需要 Context
上下文,可以写出如下代码:
// iOSMain
class Vibrator {
fun vibrate() {
/*...*/
}
}
而作为一个如此相似的工具,仅仅只有构造函数不同,有没有办法能够在 common 模块进行声明呢?答案是可以的,我们可以使用expect class
。它就像一个接口,我们分别在不同的模块利用对应的平台特性去实现它。
// commonMain
expect class Vibrator {
fun vibrate()
}
// androidMain
actual class Vibrator(private val context: Context) {
fun vibrate() { /*...*/ }
}
// iOSMain
actual class Vibrator {
fun vibrate() { /*...*/ }
}
自此我们得到了一个通用的 Vibrator
,而他们的实现和构造都由各自的平台来完成。它类似一个接口,在 common
模块无法构造。此时陷入了第二个困境:我们怎么构造这个通用的工具。
依赖注入
是的,Vibrator
也可以通过依赖注入的方式来声明和获取。
利用 Koin ,我们声明一个模块,并在模块中声明一个构造Vibrator
的函数,而这个函数交给各个平台去实现。
val vibratorModule = module { vibrator() }
expect fun Module.vibrator()
在其他平台中我们可以使用平台的服务来构造对应的Vibrator
,Context直接通过Koin依赖注入即可,这解决了一大难题。
// androidMain
actual fun Module.vibrator() {
single { Vibrator(context = get()) }
}
// iOSMain
actual fun Module.vibrator() {
single { Vibrator() }
}
到这里,我们就完成了所有准备工作,只需要在使用的地方通过 Koin 注入即可。
使用
这里以 Compose为例,以下声明一个获取 Vibrator
的函数。
@Composable
fun rememberVibrator(): Vibrator = koinInject()
然后我们在页面中可以直接使用了,非常简单!
@Composable
fun MainScreen() {
val vibrator: Vibrator = rememberVibrator()
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(onClick = { vibrator.vibrate() }) {
Text(text = "Click me to vibrate")
}
}
}
总结
通过依赖注入,我们解决了KMP 中的一个痛点,这使我们入门极为顺利!
源码
代码已开源:github.com/MReP1/Goose…