前言
在之前骑晶,我們已經(jīng)體驗(yàn)了Compose for Desktop 與 Compose for Web耻煤,目前Compose for iOS 已經(jīng)有尚未開放的實(shí)驗(yàn)性API媚送,樂觀估計(jì)今年年底將會(huì)發(fā)布Compose for iOS刃泌。同時(shí)Kotlin也表示將在2023年發(fā)布KMM的穩(wěn)定版本弄慰。
屆時(shí)Compose-jb + KMM 將實(shí)現(xiàn)Kotlin全平臺(tái)架忌。
搭建項(xiàng)目
創(chuàng)建項(xiàng)目
因?yàn)槟壳癈ompose for iOS階段還在試驗(yàn)階段吞彤,所以我們無法使用Android Studio或者IDEA直接創(chuàng)建Compose支持iOS的項(xiàng)目,這里我們采用之前的方法叹放,先使用Android Studio創(chuàng)建一個(gè)KMM項(xiàng)目饰恕,如果你不知道如何創(chuàng)建一個(gè)KMM項(xiàng)目,可以參照之前的這篇文章KMM的初次嘗試~ 井仰,項(xiàng)目目錄結(jié)構(gòu)如下所示埋嵌。
創(chuàng)建好KMM項(xiàng)目后我們需要添加Compose跨平臺(tái)的相關(guān)配置。
添加配置
首先在settings.gradle文件中聲明compose插件俱恶,代碼如下所示:
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
plugins {
val composeVersion = extra["compose.version"] as String
id("org.jetbrains.compose").version(composeVersion)
}
}
這里compose.version的版本號(hào)是聲明在gradle.properties中的雹嗦,代碼如下所示:
compose.version=1.3.0
然后我們?cè)趕hared模塊中的build文件中引用插件
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("org.jetbrains.compose")
}
并為commonMain添加compose依賴,代碼如下所示:
val commonMain by getting {
dependencies {
implementation(compose.ui)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.runtime)
}
}
sync之后合是,你會(huì)發(fā)現(xiàn)一個(gè)錯(cuò)誤警告:uikit還處于試驗(yàn)階段并且有許多bug....
uikit就是compose-jb暴露的UIKit對(duì)象了罪。為了能夠使用,我們需要在gradle.properties文件中添加如下配置:
org.jetbrains.compose.experimental.uikit.enabled=true
添加好配置之后聪全,我們先來運(yùn)行下iOS項(xiàng)目泊藕,確保添加的配置是無誤的。
果然难礼,不運(yùn)行不知道娃圆,一運(yùn)行嚇一跳
這個(gè)問題困擾了我兩三天,實(shí)在是無從下手蛾茉,畢竟現(xiàn)在相關(guān)的資料很少讼呢,經(jīng)過N次的搜索,最終解決的方案很簡(jiǎn)單:Kotlin版本升級(jí)至1.8.0就可以了谦炬。
kotlin("android").version("1.8.0").apply(false)
再次運(yùn)行項(xiàng)目悦屏,結(jié)果如下圖所示。
不過這是KMM的iOS項(xiàng)目,接下來我們看如何使用Compose編寫iOS頁(yè)面础爬。
開始iOS之旅
我們替換掉iOSApp.swift中的原有代碼散劫,替換后的代碼如下所示:
import UIKit
import shared
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let mainViewController = Main_iosKt.MainViewController()
window?.rootViewController = mainViewController
window?.makeKeyAndVisible()
return true
}
}
上面的代碼看不懂沒關(guān)系,我們只來看獲取mainViewController的這一行
let mainViewController = Main_iosKt.MainViewController()
Main_iosKt.MainViewController是通過新建在shared模塊iOSMain目錄下的main.ios.kt文件獲取的幕帆,代碼如下所示:
fun MainViewController(): UIViewController =
Application("Login") {
//調(diào)用一個(gè)Compose方法
}
接下來所有的事情就都可以交給Compose了。
實(shí)現(xiàn)一個(gè)登錄頁(yè)面
因?yàn)轫?yè)面這部分是公用的赖条,所以我們?cè)趕hared模塊下的commonMain文件夾下新建Login.kt文件失乾,編寫一個(gè)簡(jiǎn)單的登錄頁(yè)面,代碼如下所示:
@Composable
internal fun login() {
var userName by remember {
mutableStateOf("")
}
var password by remember {
mutableStateOf("")
}
Surface(modifier = Modifier.padding(30.dp)) {
Column {
TextField(userName, onValueChange = {
userName = it
}, placeholder = { Text("請(qǐng)輸入用戶名") })
TextField(password, onValueChange = {
password = it
}, placeholder = { Text("請(qǐng)輸入密碼") })
Button(onClick = {
//登錄
}) {
Text("登錄")
}
}
}
}
上述代碼聲明了一個(gè)用戶名輸入框纬乍、密碼輸入框和一個(gè)登錄按鈕碱茁,就是簡(jiǎn)單的Compose代碼。
然后需要在main.ios.kt中調(diào)用這個(gè)login方法:
fun MainViewController(): UIViewController =
Application("Login") {
login()
}
運(yùn)行iOS程序仿贬,效果如下圖所示:
嗯~纽竣,Compose 在iOS上UI幾乎可以做到100%復(fù)用,還有不學(xué)習(xí)Compose的理由嗎茧泪?
實(shí)現(xiàn)一個(gè)雙端網(wǎng)絡(luò)請(qǐng)求功能
在之前的第1彈和第2彈中蜓氨,我們分別實(shí)現(xiàn)了在Desktop、和Web端的網(wǎng)絡(luò)請(qǐng)求功能队伟,現(xiàn)在我們對(duì)之前的功能在iOS上再次實(shí)現(xiàn)穴吹。
添加網(wǎng)絡(luò)請(qǐng)求配置
首先在shared模塊下的build文件中添加網(wǎng)絡(luò)請(qǐng)求相關(guān)的配置,這里網(wǎng)絡(luò)請(qǐng)求我們使用Ktor嗜侮,具體的可參照之前的文章:KMM的初次嘗試~
配置代碼如下所示:
val commonMain by getting {
dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:$ktorVersion")
}
}
添加接口
這里我們?nèi)匀皇褂谩竪android」中的每日一問接口 :wanandroid.com/wenda/list/…
DemoReqData與之前系列的實(shí)體類是一樣的港令,這里就不重復(fù)展示了。
創(chuàng)建接口地址類锈颗,代碼如下所示顷霹。
object Api {
val dataApi = "https://wanandroid.com/wenda/list/1/json"
}
創(chuàng)建HttpUtil類,用于創(chuàng)建HttpClient對(duì)象和獲取數(shù)據(jù)的方法击吱,代碼如下所示淋淀。
class HttpUtil {
private val httpClient = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
/**
* 獲取數(shù)據(jù)
*/
suspend fun getData(): DemoReqData {
val rockets: DemoReqData =
httpClient.get(Api.dataApi).body()
return rockets
}
}
這里的代碼我們應(yīng)該都是比較熟悉的,僅僅是換了一個(gè)網(wǎng)絡(luò)請(qǐng)求框架而已∫逃担現(xiàn)在公共的業(yè)務(wù)邏輯已經(jīng)處理好了绅喉,只需要頁(yè)面端調(diào)用方法然后解析數(shù)據(jù)并展示即可。
編寫UI層
由于Android叫乌、iOS柴罐、Desktop三端的UI都是完全復(fù)用的,所以我們將之前實(shí)現(xiàn)的UI搬過來即可憨奸。代碼如下所示:
Column() {
val scope = rememberCoroutineScope()
var demoReqData by remember { mutableStateOf(DemoReqData()) }
Button(onClick = {
scope.launch {
try {
demoReqData = HttpUtil().getData()
} catch (e: Exception) {
}
}
}) {
Text(text = "請(qǐng)求數(shù)據(jù)")
}
LazyColumn {
repeat(demoReqData.data?.datas?.size ?: 0) {
item {
Message(demoReqData.data?.datas?.get(it))
}
}
}
}
獲取數(shù)據(jù)后革屠,通過
Message方法
將數(shù)據(jù)展示出來,這里只將作者與標(biāo)題內(nèi)容顯示出來,代碼如下所示似芝。
@Composable
fun Message(data: DemoReqData.DataBean.DatasBean?) {
Card(
modifier = Modifier
.background(Color.White)
.padding(10.dp)
.fillMaxWidth(), elevation = 10.dp
) {
Column(modifier = Modifier.padding(10.dp)) {
Text(
text = "作者:${data?.author}"
)
Text(text = "${data?.title}")
}
}
}
分別運(yùn)行iOS那婉、Android程序,點(diǎn)擊請(qǐng)求數(shù)據(jù)按鈕党瓮,結(jié)果如下圖:
這樣我們就用一套代碼详炬,實(shí)現(xiàn)了在雙端的網(wǎng)絡(luò)請(qǐng)求功能。
一個(gè)尷尬的問題
我一直認(rèn)為存在一個(gè)比較尷尬的問題寞奸,那就是像上面實(shí)現(xiàn)一個(gè)完整的雙端網(wǎng)絡(luò)請(qǐng)求功能需要用到KMM + Compose-jb呛谜,但是KMM與Compose-jb并不是一個(gè)東西,但是用的時(shí)候呢基本上都是一起用枪萄。Compose-jb很久之前已經(jīng)發(fā)了穩(wěn)定版本只是Compose-iOS目前還沒有開放出來隐岛,而KMM當(dāng)前還處于試驗(yàn)階段,不過在2023年Kotlin的RoadMap中瓷翻,Kotlin已經(jīng)表示將會(huì)在23年中發(fā)布第一個(gè)穩(wěn)定版本的KMM聚凹。而Compose for iOS何時(shí)發(fā)布,我想也是指日可待的事情齐帚。
所以妒牙,這個(gè)系列我覺得改名為:Kotlin跨平臺(tái)系列更適合一些,要不然以后就會(huì)存在KMM跨平臺(tái)第n彈童谒,Compse跨平臺(tái)第n彈....
因此单旁,從第四彈開始,此系列將更名為:Kotin跨平臺(tái)第N彈:~
寫在最后
從自身體驗(yàn)來講饥伊,我覺得KMM+Compose-jb 對(duì)Android開發(fā)者來說是非常友好的象浑,不需要像Flutter那樣還需要額外學(xué)習(xí)Dart語言。所以琅豆,你覺得距離Kotlin一統(tǒng)“江山”的日子還會(huì)遠(yuǎn)嗎愉豺?