Compose跨平臺(tái)第三彈:體驗(yàn)Compose for iOS

前言

在之前骑晶,我們已經(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)嗎愉豺?

作者:黃林晴
鏈接:https://juejin.cn/post/7195770699524751421

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市茫因,隨后出現(xiàn)的幾起案子蚪拦,更是在濱河造成了極大的恐慌,老刑警劉巖冻押,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驰贷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡洛巢,警方通過查閱死者的電腦和手機(jī)括袒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稿茉,“玉大人锹锰,你說我怎么就攤上這事芥炭。” “怎么了恃慧?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵园蝠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我痢士,道長(zhǎng)彪薛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任怠蹂,我火速辦了婚禮陪汽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褥蚯。我一直安慰自己,他們只是感情好况增,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布赞庶。 她就那樣靜靜地躺著,像睡著了一般澳骤。 火紅的嫁衣襯著肌膚如雪歧强。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天为肮,我揣著相機(jī)與錄音摊册,去河邊找鬼。 笑死颊艳,一個(gè)胖子當(dāng)著我的面吹牛茅特,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棋枕,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼白修,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了重斑?” 一聲冷哼從身側(cè)響起兵睛,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窥浪,沒想到半個(gè)月后祖很,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漾脂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年假颇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片符相。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拆融,死狀恐怖蠢琳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镜豹,我是刑警寧澤傲须,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站趟脂,受9級(jí)特大地震影響泰讽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昔期,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一已卸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硼一,春花似錦累澡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至哼蛆,卻和暖如春蕊梧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腮介。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工肥矢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叠洗。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓甘改,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親灭抑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子楼誓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容