Reputation: 5694
I am investigating the new Android Room Paging library
implementation "androidx.paging:paging-runtime-ktx:3.0.0-alpha09"
My source database tables approx 10,000 rows and I filter by the first character of the name field as follows:-
DAO
@Query("SELECT * from citation_style WHERE citation_style_name LIKE :startsWith ORDER BY citation_style_name ASC")
fun fetch(startsWith: String): PagingSource<Int, CitationStyleDO>
Repository
fun fetch(startsWith: String): Flow<PagingData<CitationStyleDO>> {
return Pager(
PagingConfig(pageSize = 60, prefetchDistance = 30, enablePlaceholders = false, maxSize = 200)
) { database.citationStyleDao().fetch("$startsWith%") }.flow
}
ViewModel
fun fetch(startsWith: String): Flow<PagingData<CitationStyleDO>> {
return repository.fetch(startsWith).cachedIn(viewModelScope)
}
Fragment
override fun onStartsWithClicked(startsWith: String) {
lifecycleScope.launch {
viewModel.fetch(startsWith).collectLatest { adapter.submitData(it) }
}
}
Is this the correct approach of repeatedly using lifecycleScope.launch {...}
each time the Starts With character is changed?
Should I be map{}
or switchMap{}
triggered by a MutabaleLiveData<String>
for StartwWith?
Upvotes: 7
Views: 3627
Reputation: 3895
This won't work, because submitData doesn't return until PagingData
is invalidated. You might run into race scenarios where you have multiple jobs launched where PagingDataAdapter
is trying to collect from multiple PagingData
.
The more "Flow" way would be to turn your fetch calls into a flow and combine that with your Flow<PagingData>
, which will automatically propagate cancellation every time your query changes.
A couple other things:
It's recommended to let Paging do the filtering for you, as this way you can avoid re-fetching from DB every time your search changes, and also you can lean on Paging to handle configuration changes and restore state.
You should use viewLifecycleOwner
instead of lifecycleScope
directly, because you don't want paging to do work after the fragment's view is destroyed
e.g.,
ViewModel
val queryFlow = MutableStateFlow("init_query")
val pagingDataFlow = Pager(...) {
dao.pagingSource()
}.flow
// This multicasts, to prevent combine from refetching
.cachedIn(viewModelScope)
.combine(queryFlow) { pagingData, query ->
pagingData.filter { it.startsWith(query)
}
// Optionally call .cachedIn() here a second time to cache the filtered results.
Fragment
override fun onStartsWithClicked(startsWith: String) {
viewModel.queryFlow = startsWith
}
override fun onViewCreated(...) {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.pagingDataFlow.collectLatest { adapter.submitData(it) }
}
Note: You can definitely use Room to do the filtering if you want, probably the right way there is to .flatMapLatest
on the queryFlow and return a new Pager
each tine, and pass the query term to dao function that returns a PagingSource
ViewModel
queryFlow.flatMapLatest { query ->
Pager(...) { dao.pagingSource(query) }
.cachedIn(...)
}
Upvotes: 9