前言
androidx.paging是一个专门做分页管理的库,本文使用的版本是3.3.2,文章中不会介绍如何集成和使用,只探讨如何修改数据,适合对Paging 3和Kotlin Flow有一定了解的读者,下面把androidx.paging简称为Paging。
需求
需求:列表显示5个项,每项显示id和点击数量count,点击项的时候让count加一,效果如下:
准备
先把静态页面写好:
ModifyModel是列表项实体,_sourceFlow是原始数据,通过showContentView传递数据并显示UI,具体的UI实现不是重点可以忽略,代码比较简单看注释即可,不做过多解释。
Paging对外提供的数据类型是Flow<PagingData<T>>,PagingData<T>有一个扩展函数map可以转换数据:
fun <T : Any, R : Any> PagingData<T>.map(
transform: suspend (T) -> R
): PagingData<R>
先用它来转换数据,使所有项默认的count都为666:
新建一个_flow用于保存对原始数据_sourceFlow转换后的Flow,在转换时调用data.map把每一项的count值修改为666,最后把_flow传递显示,代码比较简单就不赘述了。
然而,我们的需求是点击项的时候修改数据,意味着在点击的时候需要触发Flow刷新数据,调用PagingData<T>.map动态修改。
实现
实现方案:
- 定义一个
Flow,点击项的时候向它发送数据 - 通过
Flow.combine把原始数据Flow和第1步中的Flow结合为一个新的Flow
这样子两个Flow数据变化时都会触发PagingData<T>.map修改数据,来实现一下吧。
定义类型为MutableStateFlow的属性,Map.key为项的ID,Map.value为修改后的项:
// 修改项的数据
private val _modifyFlow = MutableStateFlow<Map<String, ModifyModel>>(emptyMap())
点击的时候,向_modifyFlow发送数据:
showContentView(
flow = _flow,
onClickItem = { item ->
_modifyFlow.update {
// 修改item的count值加一
val newItem = item.copy(count = item.count + 1)
it + (item.id to newItem)
}
},
)
再把_sourceFlow和_modifyFlow结合:
// 修改的数据
private val _flow = _sourceFlow.combine(_modifyFlow) { data, modify ->
if (modify.isEmpty()) {
// modify为空,返回原始数据
data
} else {
// 如果modify中存在则优先使用modify中的数据
data.map { item ->
modify[item.id] ?: item
}
}
}
此时两个Flow只要发生变化,就会重新执行combine的转换代码块转换数据。
重新运行,看看效果:
点击之后数据可以修改了,但是下拉刷新数据之后count没有被重置为0。
这是因为_modifyFlow中的数据还在,可以在_sourceFlow有新数据时清空_modifyFlow的数据:
private val _flow = _sourceFlow
.onEach {
// 清空数据
_modifyFlow.update { emptyMap() }
}
.combine(_modifyFlow) { data, modify ->
......
}
在combine之前插入一个onEach,用于_sourceFlow有新数据时清空_modifyFlow。
重新运行,看看效果:
封装
需求已经实现了,简单封装一下:
class PagingDataModifier<T : Any>(
/** 原始数据 */
sourceFlow: Flow<PagingData<T>>,
/** 获取项的ID */
private val getID: (T) -> Any,
) {
private val _modifyFlow = MutableStateFlow<Map<Any, T>>(emptyMap())
/** 数据流 */
val flow = sourceFlow
.onEach { _modifyFlow.update { emptyMap() } }
.combine(_modifyFlow) { data, modify ->
if (modify.isEmpty()) {
data
} else {
data.map { modify[getID(it)] ?: it }
}
}
/** 更新项 */
fun update(item: T) {
_modifyFlow.update { modify ->
val id = getID(item)
if (modify[id] == item) {
// 数据未变化,不更新Flow
modify
} else {
modify + (id to item)
}
}
}
}
把获取项ID的逻辑抽取到构造方法中传入了,这样子调用update的时候只需要传入一个新的项即可,代码比较简单,就不赘述了。
封装后使用:
// 数据修改对象
private val _modifier = PagingDataModifier(
sourceFlow = createSourceFlow(),
getID = { it.id },
)
showContentView(
// 监听modifier的数据
flow = _modifier.flow,
onClickItem = { item ->
// 修改item的count值加一
_modifier.update(
item.copy(count = item.count + 1)
)
},
)
结束
除了修改项,还有删除项,插入项等场景,也可以通过上文介绍的思路实现。
感谢阅读,如果有遇到问题欢迎和作者交流。