I want to implement the following scenario. I want to have a Paging3 recyclerView with checkboxes and multiselect items and also apply a filter on the request which should trigger the refresh of the data.
I have a request to fetch some transactions and in the request i pass a dateInterval object
DateInterval(
dateFrom: Date?,
dateTo: Date = Calendar.getInstance().time // which is always the current datetime
)
DateFrom can be chosen through a filter by a list of a RadioButtons from a BottomSheet

By default Current month is chosen. So the PagingSource is as the following
class AvailableTransactionsPagingSource(
...data...
) : PagingSource<PagingInfoDomainModel, DiffUiItem>() {
override fun getRefreshKey(state: PagingState<PagingInfoDomainModel, DiffUiItem>): PagingInfoDomainModel? =
null
override suspend fun load(params: LoadParams<PagingInfoDomainModel>): LoadResult<PagingInfoDomainModel, DiffUiItem> {
val nextPageKey = params.key ?: createDefaultPagingInfo()
when (
getTransactionsUseCase(...data...)
) {
is DomainResult.Error -> {
return showEmptyTransactions()
}
is DomainResult.Success -> {
if (params.key == null && response.data.availableTransactions.isEmpty())
return showEmptyTransactions()
val list = mutableListOf<TransactionWithCheckbox>()
response.data.availableTransactions.forEach {
list.add(
TransactionWithCheckbox(
uniqueId = it.internalId ?: "",
title = "it.description",
subtitle = it.transactionTimestampLocal,
isChecked = false
)
)
}
return LoadResult.Page(
data = list,
prevKey = null,
nextKey = getNextKey(response.data.pageInfo)
)
}
null -> {
return showEmptyTransactions()
}
}
}
private var dateInterval = DatePeriodDomainModel(
dateFrom = null,
dateTo = Calendar.getInstance().time
)
fun setDate(interval: DatePeriodDomainModel) {
dateInterval = interval
}
override val keyReuseSupported: Boolean
get() = true
....
Here i create a list of TransactionWithCheckbox models. The field isChecked will determine if the checkBox inside the onBind will be checked or not. So at the beginning all the checkboxes should be unchecked, thus i pass a false value. The dateInterval holds the dateInterval that i want the requests to be made with.
Inside the ViewModel i create the PagingSource instance and pass it the to the Pager which i collectLatest in my Fragment
@HiltViewModel
class TransactionViewModel @Inject constructor(
...data...
) : BaseViewModel() {
private val _hasNextButtonEnabled = MutableStateFlow(false)
override val hasNextButtonEnabled = _hasNextButtonEnabled.asStateFlow()
var timeWindowRadioButtonList = buildTimeWindowList(resources)
private val _selectedTimeWindow: MutableStateFlow<RadioButtonUiItem?> = MutableStateFlow(timeWindowRadioButtonList.first())
val selectedTimeWindow = _selectedTimeWindow.asStateFlow()
private var datePeriod = currentMonthsTransaction()
val checkedItems = MutableStateFlow<Set<TransactionWithCheckbox>>(setOf())
var availableTransactionsPagingSource: availableTransactionsPagingSource? = null
val loadingPagerFlow = Pager(
config = PagingConfig(
pageSize = PAGE_SIZE,
enablePlaceholders = true
),
pagingSourceFactory = {
getPagingSource()
}
).flow
.cachedIn(viewModelScope)
// Combine flow pagingData with checkedItems in order to
// keep the selected items when PagingSource reloads data
.combine(checkedItems) { pagingData, checkedSet ->
pagingData.map { item ->
if (item is TransactionWithCheckbox)
item.isChecked = checkedSet.find { item.uniqueId == it.uniqueId } != null
item
}
}
/**
* Caution!!
* checkedItems.value holds a Set of the user-selected models
* However, we cannot find an item of the list inside the Set since the
* PagingAdapter reloads the items holding different instances of the same data
* Thus, we can find-modify-access the desired items through the uniqueId comparison
*/
val onCheckBoxHandler = object : CheckBoxHandler {
override fun onItemChecked(item: RowUiItem, isChecked: Boolean) {
if (item is TransactionWithCheckbox) {
val set = checkedItems.value.toMutableSet()
if (isChecked) {
val exists = checkedItems.value.find { it.uniqueId == item.uniqueId } != null
if (!exists) set.add(item)
} else {
val existing = checkedItems.value.find { it.uniqueId == item.uniqueId }
set.remove(existing)
}
checkedItems.value = set
validateNextButton()
}
}
}
private fun getPagingSource(): PagingSource<PagingInfoDomainModel, DiffUiItem> {
availableTransactionsPagingSource = AvailableTransactionsPagingSource(...)
availableTransactionsPagingSource?.setDate(datePeriod)
return disputeAvailableTransactionsPagingSource!!
}
// !!!User just Selected or Unselected a date Filter!!!
// Reset values and set datePeriod value for the new pagingSource request
fun timeWindowSelected(selectedTimeWindow: TimeWindowRowUI?) {
when (selectedTimeWindow?.id) {
TIME_WINDOW_CURRENT_MONTH -> {
checkedItems.value = setOf()
datePeriod = currentMonthsTransaction()
_selectedTimeWindow.value = timeWindowRadioButtonList[0]
}
TIME_WINDOW_LAST_3_MONTHS -> {
checkedItems.value = setOf()
datePeriod = previousMonthsTransactions(months = -3)
_selectedTimeWindow.value = timeWindowRadioButtonList[1]
}
TIME_WINDOW_LAST_6_MONTHS -> {
checkedItems.value = setOf()
datePeriod = previousMonthsTransactions(months = -6)
_selectedTimeWindow.value = timeWindowRadioButtonList[2]
}
TIME_WINDOW_LAST_12_MONTHS -> {
checkedItems.value = setOf()
datePeriod = getPreviousYearsTransactions(years = -1)
_selectedTimeWindow.value = timeWindowRadioButtonList[3]
}
null -> {
checkedItems.value = setOf()
datePeriod = getUnlimitedPreviousTransactions()
_selectedTimeWindow.value = null
timeWindowRadioButtonList.map { it.checked = false }
}
}
availableTransactionsPagingSource?.setDate(datePeriod)
validateNextButton()
}
private fun validateNextButton() {
_hasNextButtonEnabled.value = checkedItems.value.isNotEmpty()
}
}
So inside my fragment i observe the pagingFlow and submit it to the adapter.
private val onTimeWindowApplyClickListener = object : OnRadioBottomSheetButtonClick {
override fun onButtonClick(item: RadioButtonUiItem) {
if (item is TimeWindowRowUI) {
// Here i want to refresh the whole data,
// and reset the checkboxes to the false state
viewModel.timeWindowSelected(item)
adapter.refresh() // ???? Is it correct
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setUpToolbar()
viewBinding.transactionRecycler.adapter = adapter
adapter.addLoadStateListener {
viewModel.updateListLoadingState(it.source.refresh)
}
viewModel.loadingPagerFlow.collectLatest(viewLifecycleOwner) { pagingData ->
adapter.submitData(pagingData)
}
// Depending on the selected filter we appear or disappear the filter Chip
viewModel.selectedTimeWindow.collect(viewLifecycleOwner) {
it?.let {
viewBinding.transactionFilter.containerFilterChip.isVisible = true
viewBinding.transactionFilter.textFilterName.text = it.getText()
viewBinding.transactionFilter.buttonFilterCross.isVisible = true
} ?: run {
viewBinding.transactionFilter.containerFilterChip.isVisible = false
}
}
// Click on filterChip sets the dateFrom = null
viewBinding.filter.buttonFilterCross.setOnSafeClickListener {
viewModel.timeWindowSelected(null)
viewModel.availableTransactionsPagingSource?.invalidate() // ??? correct?
}
viewBinding.imageFilter.setOnSafeClickListener {
openTimeWindowBottomSheet(viewModel.timeWindowRadioButtonList)
}
}
So lets say that i choose the first 2 transactions

After i select a 12 month Filter, the recycler supposed to refresh the data with the checkedItems = setOf() and show the new data with isChecked = false. However, this happens
The request has been done, the NextButton is disabled (since the checkedItems is an empty list) but the checkboxes are still checked.
Any ideas how can i fix this issue???
