Android - Paging 3 如何修改数据

1,292 阅读3分钟

前言

androidx.paging是一个专门做分页管理的库,本文使用的版本是3.3.2,文章中不会介绍如何集成和使用,只探讨如何修改数据,适合对Paging 3Kotlin Flow有一定了解的读者,下面把androidx.paging简称为Paging

需求

需求:列表显示5个项,每项显示id和点击数量count,点击项的时候让count加一,效果如下:

录屏2024-09-22 16.25.55.gif

准备

先把静态页面写好:

image.png

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

image.png

新建一个_flow用于保存对原始数据_sourceFlow转换后的Flow,在转换时调用data.map把每一项的count值修改为666,最后把_flow传递显示,代码比较简单就不赘述了。

然而,我们的需求是点击项的时候修改数据,意味着在点击的时候需要触发Flow刷新数据,调用PagingData<T>.map动态修改。

实现

实现方案:

  1. 定义一个Flow,点击项的时候向它发送数据
  2. 通过Flow.combine把原始数据Flow和第1步中的Flow结合为一个新的Flow

这样子两个Flow数据变化时都会触发PagingData<T>.map修改数据,来实现一下吧。

定义类型为MutableStateFlow的属性,Map.key为项的IDMap.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的转换代码块转换数据。

重新运行,看看效果:

录屏2024-09-22 18.14.57.gif

点击之后数据可以修改了,但是下拉刷新数据之后count没有被重置为0

这是因为_modifyFlow中的数据还在,可以在_sourceFlow有新数据时清空_modifyFlow的数据:

private val _flow = _sourceFlow
   .onEach {
      // 清空数据
      _modifyFlow.update { emptyMap() }
   }
   .combine(_modifyFlow) { data, modify ->
      ......
   }

combine之前插入一个onEach,用于_sourceFlow有新数据时清空_modifyFlow

重新运行,看看效果:

录屏2024-09-22 18.22.33.gif

封装

需求已经实现了,简单封装一下:

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)
     )
  },
)

结束

除了修改项,还有删除项,插入项等场景,也可以通过上文介绍的思路实现。

感谢阅读,如果有遇到问题欢迎和作者交流。