NTP-48331 | bug-fix for navi scratch card paging source (#15567)
This commit is contained in:
@@ -312,7 +312,9 @@ fun RewardsBountyBoardScreen(
|
||||
},
|
||||
onScratched = { referenceId ->
|
||||
if (viewModel.scratchCardDataSource is ApiScratchCardDataSource) {
|
||||
viewModel.scratchCardPagingDataSource.scratched(referenceId)
|
||||
viewModel.scratchCardPagingDataSource.setScratchedItem(
|
||||
referenceId
|
||||
)
|
||||
}
|
||||
},
|
||||
sendBackResponse = { response ->
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
Reference in New Issue
Block a user