Kotlin Multiplatform Mobile(KMM)脱颖而出,是一种独特的跨平台技术,与市场上可用的其他解决方案区分开来。与许多其他框架不同,KMM可以仅共享平台之间的业务逻辑,而用户界面仍然是本机的完全优势。这种方法通常涉及组建三个不同的团队来开发您的应用程序:iOS,Android和共享开发人员。 iOS团队专注于iOS平台的本机UI,Android团队负责Android UI,共享的开发人员专注于制定业务逻辑。这种协作模型促进了有效的开发,尤其是当您努力最大化代码共享时。 KMM简化了共享存储库,服务和数据库,但您的技术堆栈与其原理保持一致。
KMM提出的有趣的挑战之一是iOS和Android之间的视图模型共享,这项任务带来了增加的复杂性,但带来了可观的回报。
共享视图模型的情况
我在Kotlin Multiplatform移动设备上进行了我的Side Project,Wolfie.app的过程,这是一个专为iOS和Android平台设计的创新宠物伴侣应用程序。考虑到我的应用程序的最佳技术时,我权衡了颤抖,React Native和Kotlin Multiplatform手机的优点。选择扑朔迷离或反应的本地人会损害本地用户体验的影响 - 我渴望避免的结果。我的愿景是为iOS和Android用户提供无缝的本地体验。此外,鉴于我是一个单人物处理网络,后端和移动开发的团队,因此采取完整的本地开发途径可能会消耗我所有的可用时间。这种上下文使Kotlin MultipLatform移动移动成为理想的选择。
作为iOS开发人员,我发现采用KMM的最初步骤比对于Android开发人员的陡峭得多。对于那些更倾向于Android开发的人,清单将相对较短。这是一些要考虑的关键组件:
- kotlin :基本要求。即使您在iOS开发方面有背景,Kotlin也很容易,即使在您的就职项目上也可以很容易地学习。
- Swift :iOS开发的显而易见的必要性。如果您缺乏Mac,那么收购一个就必须。
- Swiftui和JetPack组成:声明性开发范式,为UI设计带来了新的精致水平。
- coroutines :此并发框架起着关键作用,因为您的大多数逻辑,存储库和数据库操作都将依靠它。如果您拥有反应性编程的经验,那么这将成为宝贵的基础。值得注意的是,Coroutines还可以在iOS开发中找到应用。
- KTOR:您对任何API连接的坚定盟友。
除了这些必需品之外,很多图书馆都在等待您的考虑。但是,本文将专门研究共享视图模型的主题,探索两个值得注意的库和自己动手的方法。
## moko-mvvm github
tl; dr
许可证:Apache 2.0
首次公开发行是2019年10月。
Github上的760星
34公开问题
14个贡献者
在Kotlin Multiplatform Mobile(KMM)中共享视图模型时, Moko-MVVM 库(GitHub)引起了极大的关注。该图书馆拥有Apache 2.0许可,于2019年10月发布了首次公开发布。在Github上,Moko-MVVM在Github上占据了760颗星的惊人数量,展示了其在开发人员社区中的知名度。在一个多年来一直关注KMM的团队的支持下,该图书馆将自己作为有力的竞争者展示。但是,尽管它具有巨大的希望,但在采用之前有一些考虑因素。
Moko-MVVM的关键优势是致力于推进KMM生态系统的公司的强大支持。然而,存在固有的权衡,以图书馆与声明性UI实施的一致性为中心。重要的是要注意,Moko-MVVM更多地倾向于Uikit而不是Swiftui,可能会影响其与最新技术的兼容性。在我的写作时,似乎没有与Xcode 14.3.1兼容的库,或者至少在文档之后无法无缝实现兼容性。鉴于这些情况,我选择了暂时推迟对Moko-MVVM的使用。
值得注意的是,Moko-MVVM与来自同一开发人员组的另一个值得注意的库找到了协同作用: kswift (GitHub)。这个互补的图书馆提供了大量的Kotlin/本机API翻译,以增强跨平台开发。此外,它促进了对iOS的Coroutine支持,进一步简化了您的开发工作流程。
Moko-MVVM持有的承诺是不可否认的,并且与专门针对KMM的公司保持一致,这增加了其信誉。但是,对于那些投资于Swiftui并寻求与最新iOS技术的无缝兼容性的人,有必要仔细考虑其以Uikit为中心的方法。随着KMM景观的不断发展,Moko-Mvvm和Kswift等库之间的协同作用可能变得越来越引人注目。
kmm-viewModel github
tl; dr
许可证:麻省理工学院
首次公开发行于2022年12月。
353颗恒星
4个公开问题
2个贡献者
在与Kotlin Multiplatform Mobile(KMM)跨平台共享视图模型的领域中, kmm-viewModel library 库(GitHub)成为一个显着的竞争者。 KMM-ViewModel凭借MIT许可证及其在2022年12月的首次公开发布,是该领域的一个相对较新的球员,但它已经在Github上引起了353颗星的关注。虽然处于Alpha阶段,但它与Swiftui的兼容性将其与盒子隔开。此外,其麻省理工学院许可在潜在的维护工作方面具有有利的灵活性,尤其是在支持的情况下。
尽管当前具有开发阶段,KMM-ViewModel提供了一个非凡的功能:与Swiftui无缝集成,Swiftui是现代iOS应用程序开发中的关键框架。对于那些优先考虑Swiftui的声明性和直观方法来设计用户界面设计的人来说,此属性可能特别诱人。通过在KMM-ViewModel库和Swiftui之间提供直接的桥梁,开发人员可以在平台之间实现和谐的协同作用,为iOS和Android的用户提供一致且类似的本地体验。
。值得注意的是,KMM-ViewModel是一个不断发展的库,仍在穿越其alpha阶段。但是,它与Swiftui的一致性强调了其成为跨平台开发的强大工具的潜力。鉴于其相对较新的进入景观,随着进一步的成熟,监测其进展是谨慎的。
kmm-viewModel的一个有趣的方面在于它依赖 kmp-nativecoroutines 库(GitHub)。这个辅助库通过为iOS提供了现代移动应用程序开发的关键组成部分来补充KMM-ViewModel。 Coroutines对于异步编程和管理并发是关键的,而KMP-NativeCoroutines的包含则强调了对跨平台的强大开发体验的承诺。
虽然KMM-ViewModel保留在Alpha中,但它与SwiftUI的兼容性和对Coroutine支持的强调信号是无缝跨平台视图模型共享的潜在突破。随着它继续成熟并收集开发人员社区的反馈,它可能成为KMM工具包中的重要资产。
自定义解决方案:制作Coroutines支持
在您的Kotlin Multiplatform Mobile(KMM)项目中追求对iOS和Android平台的协调支持时,自定义解决方案提供了一种多功能方法。通过制定共享功能和类,您可以弥合差距并确保异步操作的无缝集成。
让我们逐步分解自定义解决方案:
1. StateFlowClass
:共享状态流
// shared/src/commonMain/kotlin/me/blanik/sample/Couritines.kt
package me.blanik.sample
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class StateFlowClass<T>(private val delegate: StateFlow<T>) : StateFlow<T> by delegate {
fun subscribe(block: (T) -> Unit) = GlobalScope.launch(Dispatchers.IO) {
delegate.collect {
block(it)
}
}
}
fun <T> StateFlow<T>.asStateFlowClass(): StateFlowClass<T> = StateFlowClass(this)
此片段建立了一个StateFlowClass
,该StateFlowClass
包裹StateFlow
并提供subscribe
函数。它采用Coroutines来弥合平台之间的差距,确保数据收集在适当的线程上发生。
2.共享调度员
// shared/src/commonMain/kotlin/me/blanik/sample/Dispatchers.kt
package me.blanik.sample.database
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
expect val Dispatchers.IO: CoroutineDispatcher
在本节中,为两个平台设置了共享调度程序。在Android上,我们对Android的Dispatchers.IO
进行了直接的映射。对于iOS,我们需要更多上下文:
3. iOS的自定义Coroutine调度员
// shared/src/iosMain/kotlin/me/blanik/sample/Disaptchers.kt
package me.blanik.sample
import kotlinx.coroutines.*
import platform.darwin.*
actual val Dispatchers.IO: CoroutineDispatcher
get() = IODispatcher
@OptIn(InternalCoroutinesApi::class)
private object IODispatcher : CoroutineDispatcher(), Delay {
// Implementation details...
}
在此代码块中,我们为iOS定义了一个自定义的Coroutine调度程序。该调度员利用iOS的调度机制来确保Coroutines在主队列上运行并适当处理延迟。
4.流动公用事业
// shared/src/commonMain/kotlin/me/blanik/sample/FlowUtils.kt
package me.blanik.sample.database
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class CFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
// Implementation details...
}
// Helper extension
internal fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)
// Remove when Kotlin's Closeable is supported in K/N
interface Closeable {
fun close()
}
此片段引入了CFlow
,该类别可以消耗Swift/Objective-C的基于流的API。它提供了一种处理这些语言订阅的干净方法。
5.特定于平台的实现
安卓:
// shared/src/androidMain/kotlin/me/blanik/sample/Dispatchers.kt
package me.blanik.sample
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
actual val Dispatchers.IO: CoroutineDispatcher
get() = Dispatchers.IO
ios:
// shared/src/iosMain/kotlin/me/blanik/sample/Disaptchers.kt
package me.blanik.sample
import kotlinx.coroutines.*
import platform.darwin.*
actual val Dispatchers.IO: CoroutineDispatcher
get() = IODispatcher
@OptIn(InternalCoroutinesApi::class)
private object IODispatcher : CoroutineDispatcher(), Delay {
// Implementation details...
}
在这里,我们通过为调度员提供特定于平台的实现来最终确定设置。对于Android,我们只需映射到Android的内置调度程序即可。对于iOS,我们深入研究了自定义调度程序,该调度器弥合了Kotlin Coroutines和iOS的调度机制之间的差距。
有了这些组件,您已经创建了一个全面的解决方案,用于管理KMM项目中的Coroutines和异步操作。这种方法使您的项目能够在iOS和Android平台上无缝处理并发性,从而促进一致的功能和性能。
实施KMM-ViewModel:逐步指南
将KMM-ViewModel纳入您的Kotlin Multiplatform Mobile(KMM)项目为跨平台的无缝视图模型打开了新的途径。让我们逐步研究实施过程:
- 将您的项目升级到Kotlin 1.9:
在您的build.gradle.kts
文件中,将kotlin版本更新为1.9.0:
plugins {
id("com.android.application").version("8.1.0").apply(false)
id("com.android.library").version("8.1.0").apply(false)
kotlin("android").version("1.9.0").apply(false)
kotlin("multiplatform").version("1.9.0").apply(false)
}
- 将所需库添加到您的共享模块:
更新您的shared/build.gradle.kts
文件以包括必要的依赖项:
sourceSets {
val multiplatformSettingsVersion = "1.0.0"
val kmmViewModelVersion = "1.0.0-ALPHA-12"
all {
languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
}
val commonMain by getting {
dependencies {
implementation("com.russhwolf:multiplatform-settings-no-arg:$multiplatformSettingsVersion")
implementation("com.russhwolf:multiplatform-settings-serialization:$multiplatformSettingsVersion")
implementation("com.russhwolf:multiplatform-settings-coroutines:$multiplatformSettingsVersion")
implementation("com.rickclephas.kmm:kmm-viewmodel-core:$kmmViewModelVersion")
}
}
}
- 同步您的gradle文件:
添加新依赖项后确保同步您的gradle文件。
- 更新iOS的
Podfile
:
在您的iosApp
目录中,更新Podfile
以包括所需的豆荚:
target 'iosApp' do
use_frameworks!
platform :ios, '14.1'
pod 'shared', :path => '../shared'
pod 'KMPNativeCoroutinesAsync', '1.0.0-ALPHA-13'
pod 'KMPNativeCoroutinesCombine', '1.0.0-ALPHA-13'
pod 'KMPNativeCoroutinesRxSwift', '1.0.0-ALPHA-13'
pod 'KMMViewModelSwiftUI', '1.0.0-ALPHA-12'
end
在您的iosApp
目录中运行pod install
。
- 创建您的第一个共享视图模型:
在您的共享模块中创建一个新的Kotlin文件SignInViewModel.kt
:
package me.blanik.sample
import com.rickclephas.kmm.viewmodel.*
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
import kotlinx.coroutines.flow.*
open class SignInViewModel: KMMViewModel() {
private val _email = MutableStateFlow(viewModelScope, "")
private val _password = MutableStateFlow(viewModelScope, "")
@NativeCoroutinesState
val email = _email.asStateFlow()
@NativeCoroutinesState
val password = _password.asStateFlow()
fun setEmail(email: String) {
_email.value = email
}
fun setPassword(password: String) {
_password.value = password
}
}
- 添加iOS实现:
在您的iosApp
目录中,创建一个名为KMMViewModel.swift
的swift文件:
import KMMViewModelCore
import shared
extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { }
您还需要更新ContentView.swift
文件:
import SwiftUI
import KMMViewModelSwiftUI
import shared
extension ContentView {
class ViewModel: shared.SignInViewModel {}
}
struct ContentView: View {
@StateViewModel var viewModel = ViewModel()
var body: some View {
VStack {
List {
Section(header: Text("Input")) {
HStack {
Text("Email")
TextField("Email here", text: Binding(get: {
viewModel.email
}, set: { value in
viewModel.setEmail(email: value)
}))
}
HStack {
Text("Password")
SecureField("Type here", text: Binding(get: {
viewModel.password
}, set: { value in
viewModel.setPassword(password: value)
}))
}
}
Section(header: Text("Output")) {
HStack {
Text("Email")
Text(viewModel.email)
}
HStack {
Text("Password")
Text(viewModel.password)
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
将科特林的企业绑定到swiftui TextField
组件:
方向是一种方式沟通。您需要将它们作为@State
(或@Published
)值附加,并由iOS进行双向通信和本机支持。要存档,您可以使用创建自定义绑定作为Getter和setter。
Binding(get: {
…
}, set: { value in
viewModel.…(…)
})
- 在屏幕上创建标志:
在您的Android模块中,为登录屏幕创建一个可组合功能:
@Composable
fun SignInScreen(
signInViewModel: SignInViewModel = SignInViewModel()
) {
val emailState by signInViewModel.email.collectAsState()
val passwordState by signInViewModel.password.collectAsState()
Column {
Text("Input")
OutlinedTextField(
label = { Text(text = "Email") },
value = emailState,
onValueChange = signInViewModel::setEmail
)
OutlinedTextField(
label = { Text(text = "Password") },
value = passwordState,
onValueChange = signInViewModel::setPassword,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
Text("Output")
Text(text = emailState)
Text(text = passwordState)
}
}
- 在MainActivity中实现屏幕上的符号:
在您的android MainActivity
中,使用SignInScreen
合并功能:
class MainActivity : ComponentActivity() {
private val viewModel: SignInViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column {
SignInScreen(viewModel)
}
}
}
}
}
}
通过这些步骤,您已经成功地将KMM-ViewModel集成到了您的项目中,从而使iOS和Android平台之间的无缝视图模型共享。该综合指南应帮助您浏览实施过程,并利用Kotlin Multiplatform移动开发旅程中共享视图模型的力量。
KMM中的额外API连接
通过KTOR将API连接添加到Kotlin Multiplatform Mobile(KMM)项目可以大大增强您的应用程序的功能。让我们仔细研究将API调用集成到您现有的KMM项目中的步骤:
- 更新共享构建gradle配置:
在您的shared/build.gradle.kts
文件中,将ktor依赖项添加到您的共同来源集:
// shared/build.gradle.kts
kotlin {
// ...
sourceSets {
val ktorVersion = "2.3.3"
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
}
}
// ...
}
}
- 创建API模型:
定义您的API响应模型和有效载荷。例如,在您的共享模块中创建ApiAuthSignInResponse
和ApiAuthSignInPayload
类:
// shared/src/commonMain/kotlin/me/blanik/sample/network/ApiAuthSignIn.kt
package me.blanik.sample.network
import kotlinx.serialization.Serializable
@Serializable
data class ApiAuthSignInResponse(
val accessToken: String,
val refreshToken: String? = null
)
@Serializable
data class ApiAuthSignInPayload(
val username: String,
val password: String,
val keepSignIn: Boolean? = null,
val device: String? = null
)
- 创建API响应处理:
定义一个类以表示API错误响应和包含数据的响应。例如:
// shared/src/commonMain/kotlin/me/blanik/sample/network/ApiResponse.kt
package me.blanik.sample.network
import io.ktor.util.date.GMTDate
import kotlinx.serialization.Serializable
@Serializable
data class ApiError(
val statusCode: Int = 400,
val message: String = "",
val timestamp: String = GMTDate().toString(),
val errors: List<ApiErrorErrors>? = null
)
@Serializable
data class ApiErrorErrors(
val property: String = "",
val children: List<ApiErrorErrors> = emptyList(),
val constraints: Map<String, String> = emptyMap()
)
class ApiResponse<T>(success: T?, error: ApiError?) {
var success: T? = success
var failure: ApiError? = error
}
- 创建API服务:
使用KTOR的HTTP客户端实现您的API服务。定义一个封装API端点并处理API请求的类。例如:
// shared/src/commonMain/kotlin/me/blanik/sample/network/WolfieApi.kt
package me.blanik.sample.network
import io.ktor.client.*
import io.ktor.client.call.receive
import io.ktor.client.request.*
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.serialization.Serializable
import kotlinx.serialization.json.Json
class WolfieApi {
private val client by lazy {
HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json {
ignoreUnknownKeys = true
prettyPrint = true
})
}
}
}
suspend fun authSignIn(payload: ApiAuthSignInPayload): ApiResponse<ApiAuthSignInResponse> {
val response = client.post<HttpResponse>("$API_BASE_URL$AUTH_SIGN_IN") {
contentType(ContentType.Application.Json)
body = payload
}
return if (response.status.isSuccess()) {
ApiResponse(response.receive(), null)
} else {
ApiResponse(null, response.receive())
}
}
companion object {
private const val API_BASE_URL = "https://api-x.wolfie.app/v2"
private const val AUTH_SIGN_IN = "/auth/sign-in"
}
}
此类使用提供的有效负载定义了一个函数authSignIn
,该功能将邮政请求发送到指定的API端点。它返回包含成功响应或错误响应的ApiResponse
。
- 在共享视图模型中使用API:
将API服务集成到您的共享视图模型中。使用WolfieApi
类来拨打API调用,处理响应并相应地更新视图模型的状态。例如:
// shared/src/commonMain/kotlin/me/blanik/sample/SignInViewModel.kt
import me.blanik.sample.network.ApiAuthSignInPayload
import me.blanik.sample.network.WolfieApi
enum class ApiState {
INIT,
PENDING,
SUCCESS,
FAILURE
}
open class SignInViewModel : KMMViewModel() {
private val wolfieApi = WolfieApi()
// ...
@NativeCoroutinesState
val state = _state.asStateFlow()
@NativeCoroutinesState
val errorMessage = _errorMessage.asStateFlow()
// ...
suspend fun signIn() {
_state.value = ApiState.PENDING
val response = wolfieApi.authSignIn(
ApiAuthSignInPayload(
username = _email.value,
password = _password.value
)
)
if (response.success != null) {
_state.value = ApiState.SUCCESS
} else {
_state.value = ApiState.FAILURE
_errorMessage.value = response.failure?.message ?: null
}
}
}
- 更新iOS实施:
在您的iOS实现(SWIFT)中,您可以显示API状态和错误消息:
// iosApp/iosApp/ContentView.swift
// ...
struct ContentView: View {
// ...
var body: some View {
VStack {
// ...
Section(header: Text("Output")) {
// ...
HStack {
Text("State")
Text(viewModel.state.rawValue)
}
HStack {
Text("Error message")
Text(viewModel.errorMessage ?? "—")
}
}
// ...
Section(header: Text("Action")) {
Button("Sign in") {
Task {
try await viewModel.signIn()
}
}
}
}
}
}
- 更新Android实现:
在您的Android实施中,您还可以显示API状态和错误消息:
// androidApp/src/main/java/me/blanik/sample/android/MainActivity.kt
// ...
@Composable
fun SignInScreen(
signInViewModel: SignInViewModel = SignInViewModel()
) {
// ...
val stateState by signInViewModel.state.collectAsState()
val errorMessage by signInViewModel.errorMessage.collectAsState()
Column {
// ...
Text("Output")
Text(text = stateState.name)
Text(text = errorMessage ?: "—")
// ...
Button(onClick = {
GlobalScope.async(Dispatchers.Main) {
signInViewModel.signIn()
}
}) {
Text(text = "Sign in")
}
}
}
通过这些步骤,您使用KTOR成功地将API连接整合到了KMM项目中。您现在可以从共享视图模型中拨打API调用,并处理iOS和Android平台的响应。
概括
Kotlin Multiplatform Mobile(KMM)正在迅速发展,并且已成为生产应用程序的可行选择。社区正在增长,各种图书馆正在出现,以解决不同的用例,每个用例都有自己的优势和考虑因素。采用KMM时,仔细评估项目需求并选择正确的解决方案很重要。
在本指南中,我们探讨了如何使用KMM-ViewModel库在iOS和Android应用程序之间共享视图模型。通过创建共享的视图模型并使用KMM-ViewModel库,您可以确保您的业务逻辑在平台之间保持一致,同时允许本机开发人员专注于UI开发。请记住,共享模块开发人员需要在两个平台中进行精心处理,以有效地支持和维护共享代码库。
您可以在本指南GitHub上找到代码示例和实现详细信息。如果您还有其他问题或需要帮助,请随时通过LinkedIn与我联系。
kmm提供了一种有希望的方法来构建跨平台应用程序,随着生态系统的不断发展,这是一个令人兴奋的空间。