Mais conteúdo relacionado Semelhante a [GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式 (20) Mais de Shengyou Fan (17) [GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式6. 透過 Kotlin Multiplatform 共⽤業務邏輯
—
Server Web Desktop Android iOS
OS API Browser API OS API Android API iOS API
以 Kotlin Multiplatform 共⽤業務邏輯
共⽤ UI?
7. 從 Jetpack Compose 到 Compose Multiplatform
—
多平台
Jetpack Compose
建立⼿機應⽤程式介⾯
可是缺 iOS 啊?
🙄
Compose for Web
Compose for Desktop
建立桌⾯應⽤程式介⾯ 建立網路應⽤程式介⾯
8. Compose for iOS 來啦!
—
https://youtu.be/FWVi4aV36d8
• KotlinConf’23 ⼤會發佈 Alpha 版
• 預計 2024 年發佈 Beta 版
9. Kotlin Multiplatform 全版圖
—
Server Web Desktop Android iOS
OS API Browser API OS API Android API iOS API
以 Kotlin Multiplatform 共⽤業務邏輯
以 Compose Multiplatform 共⽤ UI
Android View
Swing SwiftUI
12. 本⽇範例
—
• Android
• iOS
• Desktop
• Backend API
Backend
Desktop
Android iOS
HTTPs Request/Response
JSON
Client
Server
15. ⽰範重點
—
• 多平台共⽤ UI - Compose Multiplatform
• 平台專⽤ API - 偵測平台名稱
• 共享業務邏輯 - ViewModel、HTTP、Data Class
• 整合 Multiplatform 函式庫 - Ktor Client、Voyager、Kamel
• 後端 API 服務 - Ktor Server
16. 建立開發環境
—
• Mac with macOS
• JDK
• Android Studio
• Xcode (+ SDK)
• Cocoapods
透過 Homebrew 安裝 kdoctor,可檢查環境是否符合 KMP 開發需求?
kdoctor 指令⼯具
18. • 開啟 Kotlin Multiplatform Wizard
• 勾選⽬標平台
• 下載 Zip 檔
• 解壓縮
• 以 JetBrains Fleet 開啟專案
建立 Kotlin Multiplatform 專案
—
https:
/
/
kmp.jetbrains.com
19. 專案資料夾結構
—
• composeApp → Compose 多平台主程式
- commonMain → 多平台共⽤實作
- androidMain → Android 平台專⽤實作
- iosMain → iOS 平台專⽤實作
- desktopMain → JVM/Desktop 平台專⽤實作
• iosApp → iOS 主程式進入點
• server → 後端 API 主程式
👈
20. • Ktor - HTTP Client
• kotlinx.serialization - JSON serialization/deserialization
• kotlinx.coroutines - Coroutine
• Voyager - Navigation、ViewModel
• Kamel - Asynchronous Media Loading
整合 Multiplatform 函式庫
—
21. 安裝相依套件
—
kotlin {
sourceSets {
val desktopMain by getting
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
@OptIn(ExperimentalComposeLibrary
:
:
class)
implementation(compose.components.resources)
implementation("cafe.adriel.voyager:voyager-navigator:$ver")
implementation("media.kamel:kamel-image:$ver")
implementation("io.ktor:ktor-client-core:$ver")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$ver")
implementation("io.ktor:ktor-client-content-negotiation:$ver")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ver")
}
androidMain.dependencies {
implementation(libs.compose.ui)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.androidx.activity.compose)
implementation("io.ktor:ktor-client-okhttp:$ver")
}
iosMain.dependencies {
22. 後端 API - Ktor Server
—
• 語法簡單易上⼿
• 輕量級 Web 框架
• ⽀援 Async 功能
23. 共⽤ Data Class
—
@Serializable
data class LoginRequest(
val username: String,
val password: String,
)
@Serializable
data class LoginResponse(
val result: Boolean,
val message: String,
val user: User? = null,
)
@Serializable
data class User(
val id: Int,
val username: String,
val password: String,
val email: String,
val displayName: String,
val profileImageUrl: String,
)
composeApp
- commonMain
- androidMain
- iosMain
- desktopMain
24. Ktor Server
—
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(ContentNegotiation) {
json()
}
// Ktor plugins
configureLogin()
}.start(wait = true)
}
25. 實作 API Service
—
fun Application.configureLogin() {
routing {
post("...") {
// 接收 HTTP Request
// 反序列化成 Data Class
val req = call.receive<LoginRequest>()
// 登入驗證程式碼
// 回傳 HTTP Response
call.respond(
LoginResponse(
result = ...,
message = "...",
user = loggedInUser,
)
)
}
}
}
28. 各區塊元件化
—
Logo()
DemoOnText()
Spacer()
LoginForm(
/
*
.
.
.
*
/
)
Spacer()
PasswordForgetLink()
SignUpLink()
TextField(
value =
.
.
.
,
label = { Text(text = "Username") },
onValueChange = {
/
*
.
.
.
*
/
},
modifier =
.
.
.
,
)
TextField(
value =
.
.
.
,
label = { Text(text = "Password") },
visualTransformation =,
keyboardOptions =,
onValueChange = {
/
*
.
.
.
*
/
},
modifier =
.
.
.
,
)
Button(
onClick = {
/
*
.
.
.
*
/
},
shape = RoundedCornerShape(50.dp),
modifier =
.
.
.
,
) {
Text(text = "Login")
}
30. 排版差異化 (Desktop)
—
Row(
modifier =
.
.
.
,
horizontalArrangement = Arrangement.Center
) {
Column(
modifier =
.
.
.
,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
/
/
.
.
.
}
Column(
modifier =
.
.
.
,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
/
/
.
.
.
}
}
35. 偵測平台版本
(actual 部份)
—
// androidMain
actual fun getPlatformName(): String =
"Andriod (API ${android.os.Build.VERSION.SDK_INT})"
// iosMain
actual fun getPlatformName(): String =
"iOS (${UIDevice.currentDevice.systemVersion})"
// desktopMain
actual fun getPlatformName(): String =
"Desktop (JVM ${Runtime.version()})"
composeApp
- commonMain
- androidMain
- iosMain
- desktopMain
36. 偵測平台版本
(UI 部份)
— @Composable
fun DemoOnText() {
Text(
text = "Demo on ${getPlatformName()}",
style = TextStyle(
fontSize =
.
.
.
,
fontFamily =
.
.
.
,
color =
.
.
.
,
),
)
}
39. 實作 ViewModel
—
class LoginScreenModel :
StateScreenModel<LoginScreenModel.State>(State.Init) {
private val httpClient = HttpClient {
install(ContentNegotiation) {
json()
}
defaultRequest {
url(apiBaseUrl)
}
expectSuccess = true
}
sealed class State {
data object Init : State()
data class LoggedIn(val user: User) : State()
}
fun login(username: String, password: String) {
coroutineScope.launch {
val loginResponse: LoginResponse = httpClient.post("
.
.
.
") {
setBody(LoginRequest(username, password))
contentType(ContentType.Application.Json)
• 設定 HTTP Client
• 設定 State
• 發送 HTTP Req/Res
• 關閉 HTTP Client
40. 使⽤ ViewModel
—
object LoginScreen : Screen {
@Composable
override fun Content() {
val screenModel = rememberScreenModel { LoginScreenModel() }
val username = remember { mutableStateOf(TextFieldValue()) }
val password = remember { mutableStateOf(TextFieldValue()) }
Button(
onClick = {
screenModel.login(
username.value.text, password.value.text
)
},
shape = RoundedCornerShape(50.dp),
modifier =
.
.
.
,
) {
Text(text = "Login")
}
}
}
41. 依狀態換⾴
—
object LoginScreen : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { LoginScreenModel() }
val state by screenModel.state.collectAsState()
val username = remember { mutableStateOf(TextFieldValue()) }
val password = remember { mutableStateOf(TextFieldValue()) }
when (val loggedInState = state) {
is LoginScreenModel.State.Init
-
>
{}
is LoginScreenModel.State.LoggedIn
-
>
{
navigator.push(HomeScreen(loggedInState.user))
}
}
}
}
43. Kotlin Multiplatform 全版圖
—
Server Web Desktop Android iOS
OS API Browser API OS API Android API iOS API
以 Kotlin Multiplatform 共⽤業務邏輯
以 Compose Multiplatform 共⽤ UI
Android View
Swing SwiftUI
47. Place your text here
four columns
maximum
—
採⽤ Kotlin Multiplatform 技術的團隊
—
52. Kotlin 爐邊漫談 - 聆聽業界實績案例的最佳來源
—
⼿機開發編年史 (携程)
來⾃阿⾥巴巴及美团的
Kotlin Multiplatform 應⽤案例
#8
Kotlin 爐邊漫談 Podcast
https:
/
/
podcast.kotlin.tips/
#5