Compose跨平臺第三彈:體驗Compose for iOS

前言

在之前,我們已經(jīng)體驗了Compose for Desktop 與 Compose for Web,目前Compose for iOS 已經(jīng)有尚未開放的實驗性API,樂觀估計今年年底將會發(fā)布Compose for iOS。同時Kotlin也表示將在2023年發(fā)布KMM的穩(wěn)定版本。

屆時Compose-jb + KMM 將實現(xiàn)Kotlin全平臺。

搭建項目

創(chuàng)建項目

因為目前Compose for iOS階段還在試驗階段,所以我們無法使用Android Studio或者IDEA直接創(chuàng)建Compose支持iOS的項目,這里我們采用之前的方法,先使用Android Studio創(chuàng)建一個KMM項目,如果你不知道如何創(chuàng)建一個KMM項目,可以參照之前的這篇文章KMM的初次嘗試~ ,項目目錄結(jié)構(gòu)如下所示。

創(chuàng)建好KMM項目后我們需要添加Compose跨平臺的相關(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的版本號是聲明在gradle.properties中的,代碼如下所示:

compose.version=1.3.0

然后我們在shared模塊中的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之后,你會發(fā)現(xiàn)一個錯誤警告:uikit還處于試驗階段并且有許多bug....

uikit就是compose-jb暴露的UIKit對象。為了能夠使用,我們需要在gradle.properties文件中添加如下配置:

org.jetbrains.compose.experimental.uikit.enabled=true

添加好配置之后,我們先來運行下iOS項目,確保添加的配置是無誤的。

果然,不運行不知道,一運行嚇一跳

這個問題困擾了我兩三天,實在是無從下手,畢竟現(xiàn)在相關(guān)的資料很少,經(jīng)過N次的搜索,最終解決的方案很簡單:Kotlin版本升級至1.8.0就可以了。

kotlin("android").version("1.8.0").apply(false)

再次運行項目,結(jié)果如下圖所示。

不過這是KMM的iOS項目,接下來我們看如何使用Compose編寫iOS頁面。

開始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)用一個Compose方法
    }

接下來所有的事情就都可以交給Compose了。

實現(xiàn)一個登錄頁面

因為頁面這部分是公用的,所以我們在shared模塊下的commonMain文件夾下新建Login.kt文件,編寫一個簡單的登錄頁面,代碼如下所示:

@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("請輸入用戶名") })
            TextField(password, onValueChange = {
                password = it
            }, placeholder = { Text("請輸入密碼") })
            Button(onClick = {
                //登錄
            }) {
                Text("登錄")
            }
        }
    }
}

上述代碼聲明了一個用戶名輸入框、密碼輸入框和一個登錄按鈕,就是簡單的Compose代碼。

然后需要在main.ios.kt中調(diào)用這個login方法:

fun MainViewController(): UIViewController =
    Application("Login") {
        login()
    }

運行iOS程序,效果如下圖所示:

嗯~,Compose 在iOS上UI幾乎可以做到100%復(fù)用,還有不學(xué)習(xí)Compose的理由嗎?

實現(xiàn)一個雙端網(wǎng)絡(luò)請求功能

在之前的第1彈和第2彈中,我們分別實現(xiàn)了在Desktop、和Web端的網(wǎng)絡(luò)請求功能,現(xiàn)在我們對之前的功能在iOS上再次實現(xiàn)。

添加網(wǎng)絡(luò)請求配置

首先在shared模塊下的build文件中添加網(wǎng)絡(luò)請求相關(guān)的配置,這里網(wǎng)絡(luò)請求我們使用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與之前系列的實體類是一樣的,這里就不重復(fù)展示了。

創(chuàng)建接口地址類,代碼如下所示。

object Api {
    val dataApi = "https://wanandroid.com/wenda/list/1/json"
}

創(chuàng)建HttpUtil類,用于創(chuàng)建HttpClient對象和獲取數(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)該都是比較熟悉的,僅僅是換了一個網(wǎng)絡(luò)請求框架而已?,F(xiàn)在公共的業(yè)務(wù)邏輯已經(jīng)處理好了,只需要頁面端調(diào)用方法然后解析數(shù)據(jù)并展示即可。

編寫UI層

由于Android、iOS、Desktop三端的UI都是完全復(fù)用的,所以我們將之前實現(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 = "請求數(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}")
        }
    }
}

分別運行iOS、Android程序,點擊請求數(shù)據(jù)按鈕,結(jié)果如下圖:

這樣我們就用一套代碼,實現(xiàn)了在雙端的網(wǎng)絡(luò)請求功能。

一個尷尬的問題

我一直認為存在一個比較尷尬的問題,那就是像上面實現(xiàn)一個完整的雙端網(wǎng)絡(luò)請求功能需要用到KMM + Compose-jb,但是KMM與Compose-jb并不是一個東西,但是用的時候呢基本上都是一起用。Compose-jb很久之前已經(jīng)發(fā)了穩(wěn)定版本只是Compose-iOS目前還沒有開放出來,而KMM當(dāng)前還處于試驗階段,不過在2023年Kotlin的RoadMap中,Kotlin已經(jīng)表示將會在23年中發(fā)布第一個穩(wěn)定版本的KMM。而Compose for iOS何時發(fā)布,我想也是指日可待的事情。

所以,這個系列我覺得改名為:Kotlin跨平臺系列更適合一些,要不然以后就會存在KMM跨平臺第n彈,Compse跨平臺第n彈....

因此,從第四彈開始,此系列將更名為:Kotin跨平臺第N彈:~

寫在最后

從自身體驗來講,我覺得KMM+Compose-jb 對Android開發(fā)者來說是非常友好的,不需要像Flutter那樣還需要額外學(xué)習(xí)Dart語言。所以,你覺得距離Kotlin一統(tǒng)“江山”的日子還會遠嗎?

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容