在app 开发过程中,网络请求是必不可少的,那Compose Multiplatform项目中,我们该如何进行网络请求呢?今天我们就来聊一聊CMP跨平台项目中的网络请求。
想更多了解Compose Multiplatform项目的小伙伴,也可以看看其他文章
- Compose Multiplatform 之旅 — 启程
- Compose Multiplatform 之旅 — 项目初探
- Compose Multiplatform 之旅 —做一个自己的项目(别踩白块)
- Compose Multiplatform 之旅—看看大佬在做啥
- Compose Multiplatform 之旅—为什么可以跨平台
- Compose Multiplatform 之旅—声明式UI
- Compose Multiplatform 之旅—跳转、导航(Voyager)
- Compose Multiplatform 之旅 — 数据存储(multiplatform-settings、sqldelight)
- Compose Multiplatform 之旅 — 网络请求(Ktor)
- Compose Multiplatform 之旅 — 图标、图片展示(coil)
对于框架的选择,我们还是使用之前推荐的 klibs.io,搜network 关键字,找到了一个破万star的开源项目ktor。它是由JetBrains打造的异步网络框架,100% Kotlin,完美契合CMP。
依赖引入
注意需要引入serialization的plugins,不然不能生成对应的类,出现类找不到的问题。
//libs.versions.toml文件
ktor = "2.3.11"
[libraries]
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
[plugins]
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
//shared build.gradle.kts 文件
plugins {
...
alias(libs.plugins.kotlinx.serialization)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
}
androidMain.dependencies {
implementation(libs.ktor.client.android)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
jvmMain.dependencies {
implementation(libs.ktor.client.cio)
}
}
}
如果之前没有引入lifecycle-viewmodel-compose 也需要引入下,这个基于viewmodel 来更新UI,后续我们也会讲讲viewmodel
//libs.versions.toml
compose-lifecycle = "2.8.2"
lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "compose-lifecycle" }
implementation(libs.lifecycle.viewmodel.compose)
Android 还需要manifest里面声明一下权限
<uses-permission android:name="android.permission.INTERNET" />
初始化ktorClient 实例
val ktorClient = HttpClient {
install(ContentNegotiation) {
json(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}
编写对应数据类
这里我们基于wanandroid 的开放api 做一个数据的展示。
对应接口示例:www.wanandroid.com/article/lis…
根据对应的数据,可以找一个json to kotlin的网站,直接生成对应的data class,然后加上@Serializable 注解。
下面就是wanandroid的数据自动生成的data class 精简后的代码。
@Serializable
data class ApiResponse<T>(
val data: T,
val errorCode: Int,
val errorMsg: String
)
@Serializable
data class Article(
val id: Int,
val title: String,
val author: String,
val niceDate: String,
val superChapterName: String,
val chapterName: String,
val link: String
)
@Serializable
data class ArticleList(
val curPage: Int,
val datas: List<Article>,
val offset: Int,
val over: Boolean,
val pageCount: Int,
val size: Int,
val total: Int
)
创建 Repository
我们这里是只用到get请求,代码也是非常的简单,给一个url 拼接参数即可。post 请求可以看testPost方法,使用上也十分方便。
class WanAndroidRepository() {
private val baseUrl = "https://www.wanandroid.com"
//get 请求
suspend fun getArticles(page: Int = 0): ApiResponse<ArticleList> {
return ktorClient.get("$baseUrl/article/list/$page/json").body()
}
//post 请求
suspend fun testPost(page: Int = 0): ApiResponse<ArticleList> {
return ktorClient.post("$baseUrl/article/list/$page/json") {
headers {
append("X-Custom-Header", "value")
append(HttpHeaders.UserAgent, "Ktor Client")
}
contentType(ContentType.Application.Json)
setBody(xxx)
}.body()
}
}
创建 ViewModel
继承了ViewModel,使用viewModelScope 使用协程调用接口请求
class ArticleViewModel(
private val repository: WanAndroidRepository
) : ViewModel(){
private val _articles = mutableStateListOf<Article>()
val articles: List<Article> = _articles
private val _isLoading = mutableStateOf(false)
val isLoading: State<Boolean> = _isLoading
private val _error = mutableStateOf<String?>(null)
val error: State<String?> = _error
private var currentPage = 0
fun loadArticles() {
viewModelScope.launch(Dispatchers.Default) {
_isLoading.value = true
try {
val response = repository.getArticles(currentPage)
if (response.errorCode == 0) {
_articles.addAll(response.data.datas)
currentPage++
} else {
_error.value = response.errorMsg
}
} catch (e: Exception) {
_error.value = e.message ?: "Unknown error"
} finally {
_isLoading.value = false
}
}
}
}
UI展示
使用前面的导航的逻辑,在发现tab 展示创建对应的viewModel 和 ComposableUI
object FindTab: Tab {
override val options: TabOptions
@Composable
get() = TabOptions(
index = 0u,
title = "Find"
)
@Composable
override fun Content() {
val viewModel = viewModel {
ArticleViewModel(WanAndroidRepository())
}
ArticleScreen(viewModel = viewModel)
}
}
使用LazyColumn列表展示,每一个item卡片,封装成ArticleItem
@Composable
fun ArticleScreen(viewModel: ArticleViewModel) {
val articles = viewModel.articles
val isLoading = viewModel.isLoading
Column(modifier = Modifier.fillMaxSize()) {
if (isLoading.value) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
}
LazyColumn {
items(articles.size) { index ->
ArticleItem(article = articles[index])
}
}
}
LaunchedEffect(Unit) {
viewModel.loadArticles()
}
}
@Composable
fun ArticleItem(article: Article) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
elevation = 4.dp
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = article.title,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 8.dp)
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "${article.author} · ",
style = MaterialTheme.typography.caption,
color = Color.Gray
)
Text(
text = article.niceDate,
style = MaterialTheme.typography.caption,
color = Color.Gray
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "${article.superChapterName}/${article.chapterName}",
style = MaterialTheme.typography.caption,
color = Color.Blue
)
}
}
}
}
最终效果
结语
ktor 是一个强大的库,不仅可以实现我们这里的Android、iOS、桌面端,还能写后端,感兴趣的伙伴,也可以去看看它的官方文档。
目前已经提到了好几个三方库,能覆盖app开发大部分场景,有想了解跨平台其他的特性或者三方库的伙伴,可以提出来,我们一起学习,共同进步。