在Jetpak Compose中管理网络请求竟然如此简单!

写在前面

本文中提及的use开头的函数,都出自与我的 ComposeHooks 项目,它提供了一系列 React Hooks 风格的状态封装函数,可以帮你更好的使用 Compose,无需关系复杂的状态管理,专心于业务与UI组件。

这是系列文章的第7篇,前文:

在上篇文章中我提到如果你项目中使用的是retrofit,并且已经做了协程改造,那么你可以轻松的将你的网络请求改造到 Compose 中,使用状态驱动你的UI。

上一个例子比较粗糙,可能有小伙伴不理解,同时考虑到减少模板代码,我升级了 useRedux 系列钩子,下面我将演示如何在项目中轻松的使用网络请求,并且不用再担心重组导致请求状态消失!amazing!!!!

Compose 下网络请求的痛点

众所周知,Compose的组件是有状态驱动的,并且作为函数式组件,它会不断地重组。

当我们的组件不可见时,状态从状态树移除,如果想要保留状态就需要使用 ViewModel 来进行一些状态保存,但是 viewModel 本身也会因为跨页面导航丢失状态,每次再进入页面都要重新发起请求,不能保存之前的请求状态无疑是非常制杖的!

那么怎么才能丝滑的使用网络请求呢?如何避免网络请求因为重组再次发起?

答案就是上两篇文章,我们通过 ReduxProvider 将状态提升到最根部,那么全局范围内,同一个网络请求在全局使用相同的状态,就不会出现各种场景下的状态丢失了。

show time !!

1. 创建状态存储 store

// 请求的状态封装
sealed interface NetFetchResult {
    data class Success<T>(val data: T, val code: Int) : NetFetchResult
    data class Error(val msg: Throwable) : NetFetchResult
    data object Idle : NetFetchResult
    data object Loading : NetFetchResult
}

// reducer
val fetchReducer: Reducer<NetFetchResult, NetFetchResult> = { _, action ->
    action
}

// 创建存储对象
val store = createStore {
    arrayOf("fetch1","fetch2").forEach {
        named(it) {
            fetchReducer with NetFetchResult.Idle
        }
    }
}

上篇文章介绍了,在createStore函数的闭包作用域内,你可以使用中缀函数 with,来创建一条存储,并且将 reducer 函数与初始状态传递给store;

同样的你可以使用 named(alias){} 这个作用域函数,来创建一个带别名的状态存储,这里的fetch1fetch2是请求状态的别名,你应该使用有实际意义的名称。

所有的网络请求都是相同的逻辑,所以我们可以直接使用 forEach 来批量创建具有别名的状态存储;

2. 通过 ReduxProvider 暴露状态存储

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeHooksTheme {
                // provide store for all components
                ReduxProvider(store = store) {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background
                    ) {
                        useRoutes(routes = routes)
                    }
                }
            }
        }
    }
}

ReduxProvider 置于根组件,全局共享状态

3. 按需使用

@Composable
fun UseReduxFetch() {
    val fetchResult: NetFetchResult = useSelector("fetch1")
    val dispatchAsync = useDispatchAsync<NetFetchResult>("fetch1")
    Column {
        Text(text = "result: $fetchResult")
        TButton(text = "fetch") {
            dispatchAsync {
                it(NetFetchResult.Loading)
                delay(2.seconds)
                //网络请求结果
                NetFetchResult.Success("success", 200)
            }
        }
    }
}

@Composable
fun UseReduxFetch2() {
    val fetchResult: NetFetchResult = useSelector("fetch2")
    val dispatchAsync = useDispatchAsync<NetFetchResult>("fetch2")
    Column {
        Text(text = "result: $fetchResult")
        when(fetchResult) {
            is NetFetchResult.Success<*> -> {
                // 对成功结果进行转型
                val succ= fetchResult as NetFetchResult.Success<SimpleData>
                Text(text = succ.toString())
            }
            else->{}
        }
        TButton(text = "fetch2") {
            dispatchAsync {
                it(NetFetchResult.Loading)
                delay(2.seconds)
                //网络请求结果
                NetFetchResult.Success(SimpleData("Tony Stark", 53), 200)
            }
        }
    }
}

useSelector<NetFetchResult>("fetch1") 即可拿到对应别名的状态,useDispatchAsync<NetFetchResult>("fetch1") 则可以拿到对应的 异步dispatch函数;

现在你无需对你过去的网络请求做任何改动,不需要 ViewModel,不需要LaunchedEffect,直接在 dispatchAsync 中使用 retrofit 发起请求!

dispatchAsync { it->
   it(NetFetchResult.Loading)
   delay(2.seconds) //假装在进行携程上的耗时操作
   NetFetchResult.Success(SimpleData("Tony Stark", 53), 200)
}

这里的 it 是 dispatch 函数,你可以在闭包内发起状态变更,对你的网络请求进行 try-catch,然后将结果或者异常使用 NetFetchResult.SuccessNetFetchResult.Error 包装即可!

最后请说声:⌈ 多谢提升哥!⌋

状态管理三剑客

到此为止我们已经介绍了三位用于在 Compose 中进行状态管理的钩子函数:

  • useReducer:用于实践MVI,只需要传递 reducer 函数与初始状态,返回给我们状态、dispatch函数
  • useContext:用于状态提升,解耦组件之间的状态传递,底层实现是:ProvidableCompositionLocalCompositionLocalProvider
  • useSelector/useDispatch:基于 useContext 实现的的全局版本的 useReducer

探索更多

好了以上就是 hooks 1.0.10 版本带来的一点小小改动,现在你可以自信在在Compose中使用网络请求了!

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks

implementation("xyz.junerver.compose:hooks:1.0.10")

欢迎使用、勘误、pr、star。

Compose 中进行网络请求,可借助 OkHttp 库。以下是使用 KotlinJetpack Compose 演示通过 OkHttp 发送 HTTP 请求的示例,包含发送 GET 请求和 POST 请求并显示结果 [^1]。 ### GET 请求示例 ```kotlin import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var responseText by mutableStateOf("") val context = LocalContext.current Column { Text(text = "Response: $responseText") // 发起 GET 请求 CoroutineScope(Dispatchers.IO).launch { val client = OkHttpClient() val request = Request.Builder() .url("https://api.example.com/data") // 替换为实际的 API 地址 .build() client.newCall(request).execute().use { response -> if (response.isSuccessful) { val result = response.body?.string() CoroutineScope(Dispatchers.Main).launch { responseText = result ?: "No response" } } } } } } } } ``` ### POST 请求示例 ```kotlin import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.* class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var responseText by mutableStateOf("") val context = LocalContext.current Column { Text(text = "Response: $responseText") // 发起 POST 请求 CoroutineScope(Dispatchers.IO).launch { val client = OkHttpClient() val mediaType = MediaType.get("application/json; charset=utf-8") val body = RequestBody.create(mediaType, "{\"key\": \"value\"}") // 替换为实际的请求体 val request = Request.Builder() .url("https://api.example.com/submit") // 替换为实际的 API 地址 .post(body) .build() client.newCall(request).execute().use { response -> if (response.isSuccessful) { val result = response.body?.string() CoroutineScope(Dispatchers.Main).launch { responseText = result ?: "No response" } } } } } } } } ``` 另外,还可以借助 Retrofit 进行网络请求,调用服务实例中的方法执行网络请求示例如下 [^3]: ```kotlin val call = apiService.getUser(1) // 获取用户 ID 为 1 的数据 call.enqueue(object : Callback<User> { // 使用 enqueue 方法异步执行请求 override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() // 获取数据 // 处理数据 } else { // 处理错误 } } override fun onFailure(call: Call<User>, t: Throwable) { // 处理网络请求失败 } }) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值