I'm trying to use Paging 3
library to get Flow<PagingData<Scan>>
from Room
, then check if item was selected or not in the recyclerview so I'm mapping this class to another class called ScanMapper
. For achieving this mapping, whenever user marked an item as selected, I updated Map
inside a MutableStateFlow<Map<Int, State>>
. Here the Map
looks up an Index(Int)
to get the State
, State
is just an enum
class to represent State UNINITIALISED
, USELECTED
and SELECTED
.
I am setting the value of the Map to a StateFlow<Map<Int,State>>
. The problem is, I am trying to combine the Flow<PagingData<Scan>>
with the StateFlow<Map<Int,State>>
in order to also pass the State
as a parameter to the ScanMapper
class since this State
is taken from the StateFlow<Map<Int,State>>
and is not a part of the original Scan
class. But the PagingDataAdapter
seems to always get the State UNINITIALISED
despite when I'm marking item as selected on item click using the markSelected(scanId: Int)
function.
Kindly tell me what I am missing here.
UPDATE
I was able to achieve the functionality that i wanted by using a Flow<List<Scan>>
and removing the usage of Paging 3
library using a recycler adapter with DiffUtils
. Though this is not the actual solution since it eliminated the pagination using paging 3
library, but the following changes allowed me to perform item selection:
Updated Dao
@Query("SELECT * FROM scan ORDER BY timeOfStorage DESC")
fun getAllScansList(): Flow<List<Scan>>
Updated Function in 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)
}
}
}
Updated fragment code
mainViewModel.getData(requireContext()).collect {
adapter.asyncListDiffer.submitList(it as MutableList)
}
Original Question
My Scan 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 {
}
My 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
}
Converter for Scan to ScanMapper and vice-versa
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
)
My PagingSource inside Dao
@Query("SELECT * FROM scan ORDER BY timeOfStorage DESC")
fun getAllScans(): PagingSource<Int,Scan>
@Query("SELECT id FROM scan")
fun getAllIds(): List<Int>
Getting flow using 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)
}
Initialising the Map for ids present in Room
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
}
Function in ViewModel that gets the flow and combines them
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
is a StateFlow<PagingData<ScanMapper>?>
.
This is how I am updating the Map
when an item gets selected by the user in recyclerview.
fun markSelected(scanId: Int) {
val map = _myMap.value.toMutableMap()
map[scanId] = State.SELECTED
_myMap.value = map
}
Then, I am collecting this myFlow
in my fragment
like this:
lifecycleScope.launchWhenStarted {
mainViewModel.getScansSyncFlow(requireContext())
mainViewModel.myFlow.collect { data ->
data?.let {
adapter.submitData(it)
}
}
}
The immediate issue I see is that you are using collect instead of collectLatest. Since submitData does not return, you will never receive updates from your Flow.
mainViewModel.myFlow.collectLatest {
adapter.submitData(it)
}