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

写在前面

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

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

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

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

Compose 下网络请求的痛点

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

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

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

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

show time !!

1. 创建状态存储 store

kotlin 复制代码
// 请求的状态封装
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 暴露状态存储

kotlin 复制代码
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. 按需使用

kotlin 复制代码
@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 发起请求!

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

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

进一步封装,自动处理Loading、Error

上边的代码,还是存在一些模板代码,例如: lodaing 状态、请求结果缺乏泛型、需要对请求手动try-catch,就如我之前评论所言,只需要简单的进行一个封装即可:

kotlin 复制代码
typealias ReduxFetch<T> = (block: suspend CoroutineScope.() -> T) -> Unit

@Composable
inline fun <reified T> useFetch(alias: String): ReduxFetch<T> {
    val dispatchAsync=  useDispatchAsync<NetFetchResult>(alias, onBefore = { it(NetFetchResult.Loading) })
    return { block ->
        dispatchAsync{
            try {
                NetFetchResult.Success(block())
            } catch (t: Throwable) {
                NetFetchResult.Error(t)
            }
        }
    }
}

这个封装非常简单,请求发出前首先 dispatch Loading状态,对网络请求的挂起函数执行try-catch,分别将结果|错误 dispatch出去即可;

将组件中原来使用 useDispatchAsync 函数的位置进行修改:

kotlin 复制代码
interface WebService {
    @GET("users/{user}")
    suspend fun userInfo(@Path("user") user: String): UserInfo
}

@Composable
fun UseReduxFetch2() {
    val fetchResult: NetFetchResult = useSelector("fetch2")
    val dispatchFetch = useFetch<UserInfo>("fetch2")
    Column {
        Text(text = "result: $fetchResult")
        TButton(text = "fetch2") {
            dispatchFetch {
                // 这里是你的 retrofit suspend 请求,返回值是上面声明的泛型
                NetApi.SERVICE.userInfo("junerver")
            }
        }
    }
}

现在我们迎来了在Compose下的终极网络请求状态管理,非常简单易用,你可以几乎没有额外成本的将你的 retrofit 网络请求迁移到 compose 下,无需 ViewModel、 无需操心重组导致的状态丢失,非常的鹅妹子嘤!!!!

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

状态管理三剑客

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

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

探索更多

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

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks

kotlin 复制代码
implementation("xyz.junerver.compose:hooks:1.0.10")

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

相关推荐
哀木14 小时前
一个简单的套壳方案,就能让你的 Agent 少做重复初始化
前端
问心无愧051314 小时前
ctf show web入门27
前端
小村儿15 小时前
给 AI Agent 装上"长期记忆":Karpathy 的 LLM Wiki 思想,我做成了工具
前端·后端·ai编程
竹林81815 小时前
用ethers.js连接MetaMask实现Web3钱包登录:从踩坑到稳定运行的完整记录
前端·javascript
heyCHEEMS15 小时前
如何用 Recast 实现静态配置文件源码级读写
前端·node.js
心连欣15 小时前
从零开始,学习所有指令!
前端·javascript·vue.js
review4454315 小时前
大模型和function calling分别是如何工作的
前端
东东同学15 小时前
耗时一个月,我把 Nuxt 首屏性能排障经验做成了一个 AI Skill
前端·agent
冴羽16 小时前
超越 Vibe Coding —— AI 辅助编程指南
前端·ai编程·vibecoding
梦想的颜色16 小时前
一天一个SKILL——前端最佳自动化测试 webapp-testing
前端·web app