Room 的分页无法正确合并 Flow<PagingData<Model>> 与其他流程
Pagination with Room not able to merge Flow<PagingData<Model>> with other flows properly
我正在尝试使用 Paging 3
库从 Room
获取 Flow<PagingData<Scan>>
,然后检查是否在 recyclerview 中选择了项目,所以我正在映射这个 class 到另一个叫 ScanMapper
的 class。为了实现这个映射,每当用户将一个项目标记为选中时,我都会在 MutableStateFlow<Map<Int, State>>
中更新 Map
。这里 Map
查找一个 Index(Int)
得到 State
, State
只是一个 enum
class 来表示状态 UNINITIALISED
, USELECTED
和 SELECTED
.
我正在将地图的值设置为 StateFlow<Map<Int,State>>
。问题是,我试图将 Flow<PagingData<Scan>>
与 StateFlow<Map<Int,State>>
结合起来,以便将 State
作为参数传递给 ScanMapper
class,因为这State
取自 StateFlow<Map<Int,State>>
而不是原始 Scan
class 的一部分。但是 PagingDataAdapter
似乎总是得到 State UNINITIALISED
尽管当我使用 markSelected(scanId: Int)
函数将项目标记为选中时单击项目。
请告诉我这里缺少什么。
更新
我能够通过使用 Flow<List<Scan>>
并使用带 DiffUtils
的回收器适配器删除 Paging 3
库的使用来实现我想要的功能。虽然这不是真正的解决方案,因为它消除了 pagination using paging 3
库,但以下更改允许我执行项目选择:
更新道
@Query("SELECT * FROM scan ORDER BY timeOfStorage DESC")
fun getAllScansList(): Flow<List<Scan>>
更新了 ViewModel 中的函数
fun getData(context: Context): Flow<List<ScanMapper>> {
val dao = ScanDatabase.invoke(context).getScanDao()
return combine(
dao.getAllScansList(),
myMap
) { scan, stateMap ->
scan.map {
it.toScanMapper(stateMap[it.id] ?: State.UNKNOWN)
}
}.flatMapLatest {
flow {
emit(it)
}
}
}
更新片段代码
mainViewModel.getData(requireContext()).collect {
adapter.asyncListDiffer.submitList(it as MutableList)
}
原题
我的扫描class:
@Entity(tableName = "scan")
@Parcelize
data class Scan (
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
val name: String,
val recognisedText: String,
val timeOfStorage: Long,
val filename: String
): Parcelable {
}
我的 ScanMapper class:
data class ScanMapper(
val id: Int,
val name: String,
val recognisedText: String,
val timeOfStorage: Long,
val filename: String,
val isSelected: State
)
enum class State{
UNINITIALISED,
SELECTED,
UNSELECTED,
UNKNOWN
}
扫描到 ScanMapper 的转换器,反之亦然
fun ScanMapper.mapToScan() =
Scan(
id = id,
name = name,
recognisedText = recognisedText,
timeOfStorage = timeOfStorage,
filename = filename
)
fun Scan.toScanMapper(isSelected: State) =
ScanMapper(
id = id,
name = name,
recognisedText = recognisedText,
timeOfStorage = timeOfStorage,
filename = filename,
isSelected = isSelected
)
我在 Dao 中的 PagingSource
@Query("SELECT * FROM scan ORDER BY timeOfStorage DESC")
fun getAllScans(): PagingSource<Int,Scan>
@Query("SELECT id FROM scan")
fun getAllIds(): List<Int>
使用 getScansFlow() 获取流量
fun getScansFlow(context: Context): Flow<PagingData<Scan>> {
val scanDao = ScanDatabase.invoke(context).getScanDao()
return Pager(
config = PagingConfig(
pageSize = 10,
maxSize = 30,
enablePlaceholders = false
),
pagingSourceFactory = { scanDao.getAllScans() }
).flow.cachedIn(viewModelScope)
}
正在为房间中存在的 ID 初始化地图
fun initList() =
viewModelScope.launch {
var idList: List<Int> = listOf()
withContext(Dispatchers.IO) {
val task = async {
return@async ScanDatabase.invoke(context).getScanDao().getAllIds()
}
idList = task.await()
}
val map = _myMap.value.toMutableMap()
for (id in idList) {
map[id] = State.UNINITIALISED
}
_myMap.value = map
}
ViewModel 中获取流并组合它们的函数
fun getScansSyncFlow(context: Context) {
viewModelScope.launch {
combine(
getScansFlow(context),
myMap
) { scan, stateMap ->
scan.map {
it.toScanMapper(stateMap[it.id] ?: State.UNKNOWN)
}
}.collect{
_myFlow.emit(it)
}
}
}
myFlow
是一个 StateFlow<PagingData<ScanMapper>?>
.
这就是当用户在 recyclerview 中选择项目时我更新 Map
的方式。
fun markSelected(scanId: Int) {
val map = _myMap.value.toMutableMap()
map[scanId] = State.SELECTED
_myMap.value = map
}
然后,我将这个 myFlow
收集到我的 fragment
中,如下所示:
lifecycleScope.launchWhenStarted {
mainViewModel.getScansSyncFlow(requireContext())
mainViewModel.myFlow.collect { data ->
data?.let {
adapter.submitData(it)
}
}
}
我看到的直接问题是您使用的是 collect 而不是 collectLatest。由于 submitData 没有 return,您将永远不会收到来自 Flow 的更新。
mainViewModel.myFlow.collectLatest {
adapter.submitData(it)
}
我正在尝试使用 Paging 3
库从 Room
获取 Flow<PagingData<Scan>>
,然后检查是否在 recyclerview 中选择了项目,所以我正在映射这个 class 到另一个叫 ScanMapper
的 class。为了实现这个映射,每当用户将一个项目标记为选中时,我都会在 MutableStateFlow<Map<Int, State>>
中更新 Map
。这里 Map
查找一个 Index(Int)
得到 State
, State
只是一个 enum
class 来表示状态 UNINITIALISED
, USELECTED
和 SELECTED
.
我正在将地图的值设置为 StateFlow<Map<Int,State>>
。问题是,我试图将 Flow<PagingData<Scan>>
与 StateFlow<Map<Int,State>>
结合起来,以便将 State
作为参数传递给 ScanMapper
class,因为这State
取自 StateFlow<Map<Int,State>>
而不是原始 Scan
class 的一部分。但是 PagingDataAdapter
似乎总是得到 State UNINITIALISED
尽管当我使用 markSelected(scanId: Int)
函数将项目标记为选中时单击项目。
请告诉我这里缺少什么。
更新
我能够通过使用 Flow<List<Scan>>
并使用带 DiffUtils
的回收器适配器删除 Paging 3
库的使用来实现我想要的功能。虽然这不是真正的解决方案,因为它消除了 pagination using paging 3
库,但以下更改允许我执行项目选择:
更新道
@Query("SELECT * FROM scan ORDER BY timeOfStorage DESC")
fun getAllScansList(): Flow<List<Scan>>
更新了 ViewModel 中的函数
fun getData(context: Context): Flow<List<ScanMapper>> {
val dao = ScanDatabase.invoke(context).getScanDao()
return combine(
dao.getAllScansList(),
myMap
) { scan, stateMap ->
scan.map {
it.toScanMapper(stateMap[it.id] ?: State.UNKNOWN)
}
}.flatMapLatest {
flow {
emit(it)
}
}
}
更新片段代码
mainViewModel.getData(requireContext()).collect {
adapter.asyncListDiffer.submitList(it as MutableList)
}
原题
我的扫描class:
@Entity(tableName = "scan")
@Parcelize
data class Scan (
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
val name: String,
val recognisedText: String,
val timeOfStorage: Long,
val filename: String
): Parcelable {
}
我的 ScanMapper class:
data class ScanMapper(
val id: Int,
val name: String,
val recognisedText: String,
val timeOfStorage: Long,
val filename: String,
val isSelected: State
)
enum class State{
UNINITIALISED,
SELECTED,
UNSELECTED,
UNKNOWN
}
扫描到 ScanMapper 的转换器,反之亦然
fun ScanMapper.mapToScan() =
Scan(
id = id,
name = name,
recognisedText = recognisedText,
timeOfStorage = timeOfStorage,
filename = filename
)
fun Scan.toScanMapper(isSelected: State) =
ScanMapper(
id = id,
name = name,
recognisedText = recognisedText,
timeOfStorage = timeOfStorage,
filename = filename,
isSelected = isSelected
)
我在 Dao 中的 PagingSource
@Query("SELECT * FROM scan ORDER BY timeOfStorage DESC")
fun getAllScans(): PagingSource<Int,Scan>
@Query("SELECT id FROM scan")
fun getAllIds(): List<Int>
使用 getScansFlow() 获取流量
fun getScansFlow(context: Context): Flow<PagingData<Scan>> {
val scanDao = ScanDatabase.invoke(context).getScanDao()
return Pager(
config = PagingConfig(
pageSize = 10,
maxSize = 30,
enablePlaceholders = false
),
pagingSourceFactory = { scanDao.getAllScans() }
).flow.cachedIn(viewModelScope)
}
正在为房间中存在的 ID 初始化地图
fun initList() =
viewModelScope.launch {
var idList: List<Int> = listOf()
withContext(Dispatchers.IO) {
val task = async {
return@async ScanDatabase.invoke(context).getScanDao().getAllIds()
}
idList = task.await()
}
val map = _myMap.value.toMutableMap()
for (id in idList) {
map[id] = State.UNINITIALISED
}
_myMap.value = map
}
ViewModel 中获取流并组合它们的函数
fun getScansSyncFlow(context: Context) {
viewModelScope.launch {
combine(
getScansFlow(context),
myMap
) { scan, stateMap ->
scan.map {
it.toScanMapper(stateMap[it.id] ?: State.UNKNOWN)
}
}.collect{
_myFlow.emit(it)
}
}
}
myFlow
是一个 StateFlow<PagingData<ScanMapper>?>
.
这就是当用户在 recyclerview 中选择项目时我更新 Map
的方式。
fun markSelected(scanId: Int) {
val map = _myMap.value.toMutableMap()
map[scanId] = State.SELECTED
_myMap.value = map
}
然后,我将这个 myFlow
收集到我的 fragment
中,如下所示:
lifecycleScope.launchWhenStarted {
mainViewModel.getScansSyncFlow(requireContext())
mainViewModel.myFlow.collect { data ->
data?.let {
adapter.submitData(it)
}
}
}
我看到的直接问题是您使用的是 collect 而不是 collectLatest。由于 submitData 没有 return,您将永远不会收到来自 Flow 的更新。
mainViewModel.myFlow.collectLatest {
adapter.submitData(it)
}