前语

在之前,咱们现已体验了Compose for Desktop 与 Compose for Web,现在Compose for iOS 现已有没有敞开的实验性API,乐观估量今年年底将会发布Compose for iOS。一起Kotlin也表明将在2023年发布KMM的安稳版别。

Compose跨平台第三弹:体验Compose for iOS

到时Compose-jb + KMM 将完成Kotlin全渠道。

Compose跨平台第三弹:体验Compose for iOS

建立项目

创立项目

由于现在Compose for iOS阶段还在实验阶段,所以咱们无法运用Android Studio或许IDEA直接创立Compose支撑iOS的项目,这儿咱们采用之前的办法,先运用Android Studio创立一个KMM项目,假如你不知道怎么创立一个KMM项目,可以参照之前的这篇文章KMM的初次测验~ ,项目目录结构如下所示。

Compose跨平台第三弹:体验Compose for iOS

创立好KMM项目后咱们需求增加Compose跨渠道的相关装备。

增加装备

首先在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之后,你会发现一个错误正告:uikit还处于实验阶段并且有许多bug….

Compose跨平台第三弹:体验Compose for iOS

uikit便是compose-jb露出的UIKit对象。为了可以运用,咱们需求在gradle.properties文件中增加如下装备:

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

增加好装备之后,咱们先来运转下iOS项目,保证增加的装备是无误的。

公然,不运转不知道,一运转吓一跳

Compose跨平台第三弹:体验Compose for iOS

这个问题困扰了我两三天,实在是无从下手,毕竟现在相关的资料很少,经过N次的查找,终究处理的方案很简略:Kotlin版别升级至1.8.0就可以了。

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

再次运转项目,成果如下图所示。

Compose跨平台第三弹:体验Compose for iOS

不过这是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
    }
}

上面的代码看不懂不要紧,咱们只来看获取mainViewController的这一行

let mainViewController = Main_iosKt.MainViewController()

Main_iosKt.MainViewController是经过新建在shared模块iOSMain目录下的main.ios.kt文件获取的,代码如下所示:

fun MainViewController(): UIViewController =
    Application("Login") {
        //调用一个Compose办法
    }

接下来一切的工作就都可以交给Compose了。

完成一个登录页面

由于页面这部分是共用的,所以咱们在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中调用这个login办法:

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

运转iOS程序,作用如下图所示:

Compose跨平台第三弹:体验Compose for iOS

嗯~,Compose 在iOS上UI简直可以做到100%复用,还有不学习Compose的理由吗?

完成一个双端网络恳求功用

在之前的第1弹和第2弹中,咱们别离完成了在Desktop、和Web端的网络恳求功用,现在咱们对之前的功用在iOS上再次完成。

增加网络恳求装备

首先在shared模块下的build文件中增加网络恳求相关的装备,这儿网络恳求咱们运用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")
    }
}

增加接口

这儿咱们仍然运用「wandroid」中的每日一问接口 :wanandroid.com/wenda/list/…

DemoReqData与之前系列的实体类是一样的,这儿就不重复展现了。

创立接口地址类,代码如下所示。

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

创立HttpUtil类,用于创立HttpClient对象和获取数据的办法,代码如下所示。

class HttpUtil {
    private val httpClient = HttpClient {
        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true
                isLenient = true
                ignoreUnknownKeys = true
            })
        }
    }
    /**
     * 获取数据
     */
    suspend fun getData(): DemoReqData {
        val rockets: DemoReqData =
            httpClient.get(Api.dataApi).body()
        return rockets
    }
}

这儿的代码咱们应该都是比较熟悉的,仅仅是换了一个网络恳求框架罢了。现在公共的事务逻辑现已处理好了,只需求页面端调用办法然后解析数据并展现即可。

编写UI层

由于Android、iOS、Desktop三端的UI都是完全复用的,所以咱们将之前完成的UI搬过来即可。代码如下所示:

Column() {
    val scope = rememberCoroutineScope()
    var demoReqData by remember { mutableStateOf(DemoReqData()) }
    Button(onClick = {
        scope.launch {
            try {
                demoReqData = HttpUtil().getData()
            } catch (e: Exception) {
            }
        }
    }) {
        Text(text = "恳求数据")
    }
    LazyColumn {
        repeat(demoReqData.data?.datas?.size ?: 0) {
            item {
                Message(demoReqData.data?.datas?.get(it))
            }
        }
    }
}

获取数据后,经过

Message办法

将数据展现出来,这儿只将作者与标题内容显示出来,代码如下所示。

@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程序,点击恳求数据按钮,成果如下图:

Compose跨平台第三弹:体验Compose for iOS
Compose跨平台第三弹:体验Compose for iOS

这样咱们就用一套代码,完成了在双端的网络恳求功用。

一个为难的问题

我一直认为存在一个比较为难的问题,那便是像上面完成一个完好的双端网络恳求功用需求用到KMM + Compose-jb,但是KMM与Compose-jb并不是一个东西,但是用的时候呢基本上都是一起用。Compose-jb好久之前现已发了安稳版别只是Compose-iOS现在还没有敞开出来,而KMM当前还处于实验阶段,不过在2023年Kotlin的RoadMap中,Kotlin现已表明将会在23年中发布第一个安稳版别的KMM。而Compose for iOS何时发布,我想也是指日可下的工作。

所以,这个系列我觉得改名为:Kotlin跨渠道系列更适合一些,要不然今后就会存在KMM跨渠道第n弹,Compse跨渠道第n弹….

因而,从第四弹开端,此系列将更名为:Kotin跨渠道第N弹:~

写在最后

从自身体验来讲,我觉得KMM+Compose-jb 对Android开发者来说是非常友爱的,不需求像Flutter那样还需求额外学习Dart语言。所以,你觉得距离Kotlin一统“江山”的日子还会远吗?