6、LiveData KTX:liveData构建器、map与switchMap的KTX写法
LiveData 这东西,大家都不陌生。从 Android Architecture Components 推出那天起,它就成了我们处理 UI 数据更新的标配。但说实话,早期用 LiveData 写代码,总觉得有点啰嗦——尤其是配合 Kotlin 协程的时候,各种回调嵌套,看着就头疼。
KTX 扩展库出来之后,情况好多了。今天我就带大家看看 LiveData KTX 里几个最实用的工具:liveData 构建器、map 和 switchMap 的 Kotlin 写法。这些 API 说白了就是帮我们把 LiveData 和协程无缝衔接起来,代码写起来更顺滑。
6.1 liveData 构建器:协程与 LiveData 的桥梁
先说说 liveData 构建器。这是 KTX 库提供的一个顶层函数,它允许我们在协程作用域内发射 LiveData 的值。我刚开始用的时候,第一反应是:这不就是 LiveData 版的 async/await 吗?
来看个例子。假设我们要从数据库加载用户列表,传统写法可能是这样:
// 传统写法
val users: LiveData<List<User>> = liveData {
val data = userDao.getAllUsers() // 挂起函数
emit(data)
}
嗯,就这么简单。liveData 构建器内部自动帮我们创建了一个协程,我们只需要在代码块里调用挂起函数,然后用 emit() 把结果发出去就行。它会在 LiveData 变为活跃状态时启动协程,不活跃时自动取消。
我在项目中遇到过一种情况:需要先从本地缓存加载数据,再请求网络更新。用 liveData 构建器处理这种场景特别顺手:
val users: LiveData<List<User>> = liveData {
// 先发射缓存数据
val cached = userCache.getUsers()
if (cached != null) {
emit(cached)
}
// 再请求网络
try {
val fresh = userApi.fetchUsers()
userCache.saveUsers(fresh)
emit(fresh)
} catch (e: Exception) {
// 网络失败时,保留缓存数据
// 或者发射一个错误状态
}
}
你想想看,这种「缓存优先、网络更新」的模式,用传统方式写要多麻烦?现在几行代码就搞定了。
liveData 构建器还支持 emitSource() 方法,可以发射另一个 LiveData 源。比如你想把 Room 数据库的 Flow 转成 LiveData,可以这样写:emitSource(userDao.getAllUsersFlow().asLiveData())。
6.2 map 的 KTX 写法:数据转换更简洁
接下来聊聊 map。这个大家应该很熟悉了,就是把 LiveData 里的数据做一层转换。KTX 提供的 map 扩展函数,本质上和 Transformations.map 是一样的,但写法更 Kotlin 化。
对比一下就知道了:
// 传统写法
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
user.name
}
// KTX 写法
val userName: LiveData<String> = userLiveData.map { user ->
user.name
}
说白了就是少了一层 Transformations 的包裹,直接对 LiveData 实例调用 .map 方法。我个人习惯用 KTX 写法,因为读起来更自然——「把这个 LiveData 映射成另一个值」,语义很清晰。
不过要注意,map 是在主线程执行的。如果你的转换逻辑比较重(比如解析 JSON、做复杂计算),我建议在 liveData 构建器里用协程处理,而不是用 map。
map 里做过一次数据库查询,结果导致 UI 线程卡顿。后来才意识到,map 的转换函数是在观察者的线程执行的,默认就是主线程。所以,不要在 map 里做耗时操作。
6.3 switchMap 的 KTX 写法:动态切换数据源
switchMap 比 map 复杂一点。它的作用是:当输入 LiveData 的值发生变化时,根据新值创建一个新的 LiveData,并切换到那个新 LiveData 上。之前的 LiveData 会被取消订阅。
KTX 的 switchMap 写法同样简洁:
// 传统写法
val userDetails: LiveData<UserDetails> = Transformations.switchMap(userIdLiveData) { id ->
userRepository.getUserDetails(id)
}
// KTX 写法
val userDetails: LiveData<UserDetails> = userIdLiveData.switchMap { id ->
userRepository.getUserDetails(id)
}
这里有个关键点:switchMap 的参数函数必须返回一个 LiveData 对象。我见过不少新手在这里踩坑,返回了一个普通对象,结果编译报错。
为什么会这样?因为 switchMap 的本质是「监听一个 LiveData,当它变化时,切换到另一个 LiveData」。所以它需要你提供一个「根据输入值生成新 LiveData」的函数。
我在项目中用 switchMap 最多的场景是搜索功能:
val searchQuery = MutableLiveData<String>()
val searchResults: LiveData<List<SearchItem>> = searchQuery.switchMap { query ->
if (query.isBlank()) {
// 空查询时返回空列表
MutableLiveData(emptyList())
} else {
// 根据查询词搜索
repository.search(query)
}
}
每次用户输入新关键词,switchMap 会自动取消上一次的搜索请求,切换到新的搜索 LiveData。这就避免了「先发请求 A,再发请求 B,结果 B 先回来,A 后回来把结果覆盖了」这种竞态问题。
6.4 三者对比:什么时候用哪个?
我把这三个 API 的适用场景整理了一下,方便大家对照:
| API | 适用场景 | 特点 |
|---|---|---|
liveData { } |
需要协程支持的数据加载(网络、数据库) | 自动管理协程生命周期,支持 emit 和 emitSource |
.map { } |
对 LiveData 的值做简单转换(如格式化、提取字段) | 同步执行,在主线程运行 |
.switchMap { } |
根据输入动态切换数据源(搜索、筛选、导航参数变化) | 自动取消旧 LiveData,避免竞态 |
简单来说:需要异步操作就用 liveData 构建器;只是数据格式转换就用 map;需要根据输入动态切换数据源就用 switchMap。
6.5 知识体系总览
为了让大家更直观地理解这三个 API 的关系和定位,我画了一张图:
6.6 组合使用:让代码更优雅
这三个 API 不是孤立的,它们可以组合使用。我给大家看一个实际项目中的例子:
// 输入:搜索关键词
private val searchQuery = MutableLiveData<String>()
// 第一步:用 switchMap 根据关键词切换数据源
// 第二步:在 liveData 构建器里做异步加载
// 第三步:用 map 做最终的数据格式化
val displayData: LiveData<List<DisplayItem>> = searchQuery.switchMap { query ->
liveData {
val rawResults = repository.searchItems(query)
emit(rawResults)
}
}.map { items ->
items.map { it.toDisplayItem() }
}
你看,switchMap 负责动态切换,liveData 负责异步加载,map 负责最终转换。各司其职,代码读起来一目了然。
📌 核心要点:
liveData构建器是协程和 LiveData 的桥梁,适合异步数据加载.map是同步转换,适合轻量操作.switchMap是动态切换,适合搜索、筛选等场景- 三者可以链式调用,组合使用效果更佳
好了,关于 LiveData KTX 的三个核心 API 就讲到这里。这些工具用好了,能省不少代码量,也能避免很多常见的坑。下次写 LiveData 的时候,不妨试试这些 KTX 写法,你会觉得「嗯,这才是 Kotlin 该有的样子」。