Kotlin Multiplatform项目探索之KMChat
这是当前在 Kotlin Multiplatform 官方文档中提议的用例结构。Kotlin Multiplatform 中包含的许多子组件已经发布了稳定版本,即使目前不稳定的组件也在迅速更新。
本文中的演示项目包括 Compose Multiplatform - Web (Experimental)、iOS (Alpha) 和 Kotlin Multiplatform - Kotlin/Native (Beta)。在进行产品项目开发时,使用这种方法可能会导致一些问题。
通用代码随处可见
在开发软件时,各个平台上都存在重复的实现,被称为通用代码。这项任务不仅需要开发成本,还包含了许多开销,比如通信成本。JetBrains 提议使用 Kotlin Multiplatform,可以将通用代码开发为单个代码库,有效地解决了这些问题。
KMChat
这是我实现的一个聊天应用项目,旨在更好地理解官方文档中提出的用例。通过这个项目,我将学习如何使用Kotlin Multiplatform编写通用代码,并将其应用于所有平台。
我还使用Compose Multiplatform实现了所有平台的用户界面。
预览结果
- 创建房间 → 加入房间 → 设置用户名
这是仅实现了聊天服务的最少功能的客户端的结果。您可以通过使用Compose多平台实现UI在所有平台上的结果。
Github
- https://github.com/ColaGom/km-chat-common
- https://github.com/ColaGom/km-chat-backend
- https://github.com/ColaGom/km-chat-client
工程结构
常用存储库由客户端和后端存储库中的子模块导入。
Common
- 它包括聊天服务中使用的共同代码。
- 我决定将演示项目的共同代码中包括DTO类和用例,这些类和用例同样包含在客户端和后端的业务逻辑中。
DTO
data class Chat
data class ChatMessage
data class ChatRoom
data class ChatUser
data class SendChat
data class CreateChatRoom
data class DeleteChatRoom
UseCase
interface UseCase<INPUT, OUTPUT> {
suspend fun execute(input: INPUT): OUTPUT
}
interface CreateChatRoomUseCase : UseCase<CreateChatRoom, ChatRoom>
interface GetAllChatRoomUseCase : VoidUseCase<List<ChatRoom>>
interface GetChatRoomUseCase : UseCase<Long, ChatRoom?>
OverView common
后端和客户端项目均使用UseCaseImpl
实现业务逻辑。
后端 — ktor
- 实现了聊天室和与用户相关的API功能。
- 使用WebSocket实现了聊天功能。
- 存储库采用内存方式实现存储所有数据。
客户端 — Compose Multiplatform
- 支持AOS、iOS、Web和桌面平台。
- 使用Kotlin Multiplatform实现了所有平台的客户端逻辑。
- 使用Compose Multiplatform实现了所有平台的用户界面。
我尝试了两种使用Compose Multiplatform的方式。
- 使用Compose multiplatform实现整个屏幕 (Web, Desktop)。
//in shared common
@Composable
fun ChatApp() {
var selectedRoom by remember {
mutableStateOf<ChatRoom?>(null)
}
Theme {
Surface {
Column(modifier = Modifier.fillMaxSize()) {
val room = selectedRoom
if (room != null) {
ChatScreen(room.id)
DisposableEffect(room) {
onDispose {
selectedRoom = null
}
}
} else {
ChatRoomListScreen {
selectedRoom = it
}
}
}
}
}
}
- 在本地 UI(AOS、iOS)中实现复合 UI。
class MainActivity : ComponentActivity() {
private val chatLauncher = registerForActivityResult(ChatActivity.Contract()) { }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ChatRoomListScreen(
onClickRoom = { room ->
chatLauncher.launch(room.id)
}
)
}
}
}
@main
struct iOSApp: App {
var body: some Scene {
WindowGroup {
MainView()
}
}
}
protocol ListViewListener : AnyObject {
func clickRoom(room: Km_chat_commonChatRoom)
}
class MainViewModel: ObservableObject, ListViewListener {
@Published var selectedRoom: Km_chat_commonChatRoom?
var roomId: Int64 { ... }
var entered: Bool { ... }
func clickRoom(room: Km_chat_commonChatRoom) {
selectedRoom = room
}
}
struct MainView: View{
@StateObject private var viewModel = MainViewModel()
var body: some View {
ListView(listener: viewModel)
.sheet(isPresented: $viewModel.entered, content: { ChatView(roomId: viewModel.roomId) })
}
}
struct ChatView: UIViewControllerRepresentable {
var roomId: Int64
func makeUIViewController(context: Context) -> UIViewController {
return ControllersKt.ChatController(roomId: roomId)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
struct ListView: UIViewControllerRepresentable { ... }
结论
使用Kotlin Multiplatform似乎可以减少在各个层中存在的重复代码。
以下是将Kotlin Multiplatform应用于三层分层项目的两种方式的描述。
普通结构
上图显示了一个典型工作客户端和后端的结构。我假设所有项目都是按照三层结构进行开发的。在整个层中常常存在大量重复的代码。
使用Compose多平台技术
上图显示了KMChat的结构。我使用Kotlin多平台来共享通用代码,甚至为所有客户端实现用户界面。这种方法有潜力在某些平台(Web、iOS)上显著降低用户界面的质量,与本地(Native)相比。
共享演示业务,本地用户界面
上图仅显示演示业务层作为共享模块实现,UI作为本地UI实现。它在保持本地UI开发优势的同时,减少了多个层次的代码重复。
共享业务逻辑及平台组件构建UI
对于具有稳定性的平台,使用Compose实现UI,而其他平台使用本机UI。
参考
https://kotlinlang.org/lp/multiplatform/
https://kotlinlang.org/docs/multiplatform.html
https://www.jetbrains.com/lp/compose-multiplatform/