NTP-48331 | bug-fix for navi scratch card paging source (#15567)

This commit is contained in:
Kishan Kumar
2025-03-28 17:05:37 +05:30
committed by GitHub
parent 43072996b5
commit 18473f0aa5
5 changed files with 91 additions and 80 deletions

View File

@@ -312,7 +312,9 @@ fun RewardsBountyBoardScreen(
},
onScratched = { referenceId ->
if (viewModel.scratchCardDataSource is ApiScratchCardDataSource) {
viewModel.scratchCardPagingDataSource.scratched(referenceId)
viewModel.scratchCardPagingDataSource.setScratchedItem(
referenceId
)
}
},
sendBackResponse = { response ->

View File

@@ -15,7 +15,7 @@ import com.navi.common.utils.isValidResponse
import com.navi.rr.common.constants.REWARDS_BOUNTY_BOARD_SCREEN
import com.navi.rr.scratchcard.model.ScratchCardModel
import com.navi.rr.utils.pagingsource.NaviScratchCardPagingSource
import com.navi.rr.utils.pagingsource.state.ItemState
import com.navi.rr.utils.pagingsource.state.State
class ScratchCardPagingSource(
private val repository: RewardsBountyBoardRepo,
@@ -30,7 +30,7 @@ class ScratchCardPagingSource(
return item.referenceId.orEmpty()
}
override suspend fun fetchNextPage(): List<ItemState<ScratchCardModel>> {
override suspend fun fetchNextPage(): List<State<ScratchCardModel>> {
if (!isNextPageAvailable) return emptyList()
@@ -47,6 +47,6 @@ class ScratchCardPagingSource(
if (response.isValidResponse()) {
isNextPageAvailable = response.data?.isNextPageAvailable.orTrue()
}
return response.data?.scratchCardHistoryList.orEmpty().map { ItemState(element = it) }
return response.data?.scratchCardHistoryList.orEmpty().map { State(element = it) }
}
}

View File

@@ -18,12 +18,10 @@ class ApiScratchCardDataSource(
override suspend fun getCard(): ScratchCardModelDataEntity? {
return naviScratchCardPagingSource.getNextItem()?.run {
item?.let {
ScratchCardModelDataEntity(
it.element.copy(templateName = templateName),
!isLastItem,
)
}
ScratchCardModelDataEntity(
scratchCardModel = item.element.copy(templateName = templateName),
isNextElementAvailable = !isLastItem,
)
}
}
}

View File

@@ -8,8 +8,8 @@
package com.navi.rr.utils.pagingsource
import com.navi.common.utils.log
import com.navi.rr.utils.pagingsource.state.ItemState
import com.navi.rr.utils.pagingsource.state.PagingItem
import com.navi.rr.utils.pagingsource.state.State
import java.util.concurrent.ConcurrentLinkedQueue
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
@@ -20,71 +20,78 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage: Float) {
private val coroutineScope =
CoroutineScope(
Dispatchers.IO + CoroutineExceptionHandler { _, throwable -> throwable.log() }
)
private val itemQueue: ConcurrentLinkedQueue<ItemState<Item>> = ConcurrentLinkedQueue()
private val itemQueue: ConcurrentLinkedQueue<State<Item>> = ConcurrentLinkedQueue()
private val _isQueueEmpty = MutableStateFlow(false) // StateFlow to track queue state
private val _isQueueEmpty =
MutableStateFlow(itemQueue.isEmpty()) // StateFlow to track queue state
val isQueueEmpty = _isQueueEmpty.asStateFlow()
protected abstract fun getReferenceId(item: Item): String // Abstract method to get referenceID
private fun updateQueueState() {
_isQueueEmpty.update { itemQueue.isEmpty() }
}
var isNextPageAvailable: Boolean = true
private var isLastPage: Boolean = false
protected var isNextPageAvailable: Boolean = true
private var isPrefetching: Boolean = false
private var lastPageSize: Int = 10
protected abstract suspend fun fetchNextPage(): List<ItemState<Item>>?
private val coroutineScope =
CoroutineScope(
Dispatchers.IO +
CoroutineExceptionHandler { _, throwable ->
clearData()
throwable.log()
}
)
suspend fun getNextItem(): PagingItem<ItemState<Item>?>? {
private fun updateQueueState() {
_isQueueEmpty.update { itemQueue.isEmpty() }
}
protected abstract fun getReferenceId(item: Item): String // Abstract method to get referenceID
protected abstract suspend fun fetchNextPage(): List<State<Item>>?
suspend fun getNextItem(): PagingItem<State<Item>>? {
return getNextItems()
}
private suspend fun getNextItems(): PagingItem<ItemState<Item>?>? {
var needPrefetch = false
if (itemQueue.isEmpty() && !isLastPage) {
val pageItems = fetchNextPage()
if (pageItems != null && pageItems.isEmpty()) {
isLastPage = true
} else {
pageItems?.let {
addItemToQueueUniquely(pageItems)
lastPageSize = pageItems.size
updateQueueState()
}
}
}
private suspend fun getNextItems(): PagingItem<State<Item>>? {
fetchInitialPageIfNeeded()
if (itemQueue.isEmpty()) return null
val nextItem = getNextNonPeekedItem()
val nextItem = getNextItemAndMarkPeeked() ?: return null
val thresholdCount = (lastPageSize * thresholdPercentage).toInt()
triggerPrefetchIfNeeded()
nextItem?.let { nextItem.isPeeked = true }
if (getNonPeekedItemsCount() <= thresholdCount && (!isLastPage && isNextPageAvailable)) {
needPrefetch = true
}
val isLastItem = isLastAvailableItem()
if (needPrefetch) {
return PagingItem(nextItem, isLastItem)
}
private fun fetchInitialPageIfNeeded() {
if (itemQueue.isEmpty() && isNextPageAvailable) {
isPrefetching = false
prefetchData()
}
val isLastItem = getNonPeekedItemsCount() == 0 && (isLastPage || isNextPageAvailable.not())
}
val pagingItem = PagingItem(nextItem, isLastItem)
private fun getNextItemAndMarkPeeked(): State<Item>? {
val item = getNextNonPeekedItem()
item?.isPeeked = true
return item
}
return pagingItem
private fun triggerPrefetchIfNeeded() {
val thresholdCount = lastPageSize * thresholdPercentage
val remaining = getNonPeekedItemsCount()
if (remaining <= thresholdCount && isNextPageAvailable) {
prefetchData()
}
}
private fun isLastAvailableItem(): Boolean {
return getNonPeekedItemsCount() == 0 && !isNextPageAvailable
}
private fun prefetchData() {
@@ -92,15 +99,11 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
coroutineScope.launch {
isPrefetching = true
val newItems = fetchNextPage()
if (newItems != null && newItems.isEmpty()) {
isLastPage = true
} else {
newItems?.let {
addItemToQueueUniquely(newItems)
updateQueueState()
lastPageSize = newItems.size
}
newItems?.let {
addItemToQueueUniquely(newItems)
lastPageSize = newItems.size
}
updateQueueState()
isPrefetching = false
}
}
@@ -110,27 +113,19 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
}
fun resetPeekedItems() {
removeScratched()
iterateOverQueue { item, _ -> if (item.isScratched.not()) item.isPeeked = false }
iterateOverQueue { item, _ -> item.isPeeked = item.isScratched }
updateQueueState()
}
fun clearData() {
itemQueue.clear()
isLastPage = false
isPrefetching = false
isNextPageAvailable = true
updateQueueState()
}
private fun getNextNonPeekedItem(): ItemState<Item>? {
val iterator = itemQueue.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (!item.isPeeked) {
return item
}
}
return null
private fun getNextNonPeekedItem(): State<Item>? {
return findFirstInQueue { item, _ -> !item.isPeeked }
}
private fun getNonPeekedItemsCount(): Int {
@@ -145,7 +140,7 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
return count
}
fun notPeeked(referenceId: String) {
fun setNonPeekedItem(referenceId: String) {
iterateOverQueue { item, _ ->
if (getReferenceId(item.element) == referenceId) {
item.isPeeked = false
@@ -153,7 +148,7 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
}
}
fun scratched(referenceId: String) {
fun setScratchedItem(referenceId: String) {
iterateOverQueue { item, _ ->
if (getReferenceId(item.element) == referenceId) {
item.isScratched = true
@@ -163,15 +158,19 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
}
private fun removeScratched() {
var removed = false
iterateOverQueue { item, itr ->
if (item.isScratched) {
removed = true
itr.remove()
updateQueueState()
}
}
if (removed) {
updateQueueState()
}
}
private fun addItemToQueueUniquely(pageItems: List<ItemState<Item>>) {
private fun addItemToQueueUniquely(pageItems: List<State<Item>>) {
pageItems.forEach { networkItem ->
val networkItemRefId = getReferenceId(networkItem.element)
@@ -181,6 +180,7 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
val queueItemRefId = getReferenceId(itemState.element)
if (networkItemRefId == queueItemRefId) {
itemFound = true
return@iterateOverQueue
}
}
if (!itemFound) {
@@ -190,12 +190,23 @@ abstract class NaviScratchCardPagingSource<Item>(private val thresholdPercentage
}
private fun iterateOverQueue(
action: (ItemState<Item>, iterator: MutableIterator<ItemState<Item>>) -> Unit
predicate: (State<Item>, iterator: MutableIterator<State<Item>>) -> Unit
) {
val iterator = itemQueue.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
action(item, iterator)
predicate(item, iterator)
}
}
private fun findFirstInQueue(
predicate: (State<Item>, MutableIterator<State<Item>>) -> Boolean
): State<Item>? {
val iterator = itemQueue.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (predicate(item, iterator)) return item
}
return null
}
}

View File

@@ -7,7 +7,7 @@
package com.navi.rr.utils.pagingsource.state
data class ItemState<T>(
data class State<T>(
var isPeeked: Boolean = false,
var isScratched: Boolean = false,
val element: T,