Repeated Pagination Requests on Swipe Refresh

31 views Asked by At

I'm facing an issue in my Kotlin Android app where pagination requests are made repeatedly when using Swipe Refresh. I have shared my codes below, I send page requests to the service, for example, after the first 30 items, I send page 2 requests, but if I swiperefresh, for some reason these page requests are sent one after another and in a very ridiculous way and the recyclerview scrolls to the bottom. I think that since the Recyclerview scrolls to the bottom, page requests are being sent one after the other. I couldn't solve this problem, can you help me?

BroadcasterEventsFragment

@AndroidEntryPoint
class BroadcasterEventsFragment :
    BaseFragment<FragmentBroadcasterEventsBinding, BroadcasterEventsViewModel>(
        layoutId = R.layout.fragment_broadcaster_events
    ) { ... 

   private val eventPagingAdapter: EventPagingAdapter by lazy {
        EventPagingAdapter(
            ViewType.REGULAR_SMALL_FULL_WIDTH
        )
    } 
 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupAdapter()
        setupNoContentLayout()
        viewLifecycleOwner.lifecycleScope.launchWhenResumed {
            viewModel.events.collectLatest {
                eventPagingAdapter.submitData(it)
                binding.swipeRefresh.isRefreshing = false
            }
        }

        viewModel.init(user)
    }
    private fun setupAdapter() {
        binding.recyclerView.apply {
            layoutManager = GridLayoutManager(requireContext(), 2)
            addItemDecoration(
                EventItemDecoration(extraTopMargin = -16)
            )

            setHasFixedSize(true)
            adapter = eventPagingAdapter
        }

        binding.swipeRefresh.setOnRefreshListener {
            eventPagingAdapter.refresh()
        }

        eventPagingAdapter.clickListener = { event ->
            val events = eventPagingAdapter.snapshot().items
            val index = events.indexOfFirst { it.id == event.id }

            if (index != -1) {
                activityViewModel.run {
                    cacheEvents(events)

                    when (val activity = requireActivity()) {
                        is MainActivity -> navigateToFeed(index, page = EventPageProperty.PROFILE)
                        is FeedActivity -> {
                            if (feedActivityViewModel.hasPermission) {
                                navigateToFeed(index, page = EventPageProperty.PROFILE)
                            } else {
                                activity.recreate(
                                    index,
                                    pageProperty = EventPageProperty.PROFILE
                                )
                            }
                        }
                        else -> Unit
                    }
                }
            }
        }

        eventPagingAdapter.onNotifyClicked = { event ->
            viewModel.notifyMe(event)
        }
    }
   override fun onViewEvent(viewEvent: ViewEvent) {
        when (viewEvent) {
            is NotifyMeCompleted -> eventPagingAdapter.notifyDataSetChanged()
            is NavigateToLanding -> {
                val fragmentNavigation = LandingFragment.fragmentNavigation()
                fragmentBackStackManager.showChildFragment(fragmentNavigation)
            }
            else -> super.onViewEvent(viewEvent)
        }
    }

BroadcasterEventsViewModel

@HiltViewModel
class BroadcasterEventsViewModel @Inject constructor(
    private val prefManager: IPreferenceManager,
    private val broadcasterRepository: IBroadcasterRepository,
    private val notificationRepository: INotificationRepository,
    private val notifyMeUseCase: NotifyMeUseCase,
    private val savedStateHandle: SavedStateHandle
) : BaseViewModel() {

 lateinit var events: Flow<PagingData<Event>>

  fun init(user: User) {
        this.user = user
        fetchEvents()
    }

    private fun fetchEvents() {
        val fetchStream = when {
            isSeller && tabPosition == SELLER_VIDEO_INDEX -> false
            isSeller && tabPosition == SELLER_STREAM_INDEX -> true
            !isSeller && tabPosition == NO_SELLER_VIDEO_INDEX -> false
            !isSeller && tabPosition == NO_SELLER_STREAM_INDEX -> true
            else -> false
        }
        Log.d("page error","fetchEvents count ")
        events = Pager(PagingConfig(pageSize = EVENT_DATA_PAGE_SIZE, prefetchDistance = PREFETCH_DISTANCE)) {
            BroadcasterEventsPagingSource(
                broadcasterRepository = broadcasterRepository,
                user = user,
                fetchStream = fetchStream
            )
        }.flow.cachedIn(viewModelScope)
            .combine(notifyMeUseCase.notifiedEventIdList) { pagedEvents, notifiedEventIdList ->
                pagedEvents.map { it.copy(isNotified = notifiedEventIdList?.contains(it.id)) }
            }
    }

    fun notifyMe(event: Event) {
        if (prefManager.token == null) {
            sendViewEvent(NavigateToLanding)
            return
        }

        launch(
            onError = {
                when (it.isAuthException()) {
                    true -> sendViewEvent(LiveEventsViewEvent.NavigateToLanding)
                    false -> handleException(it)
                }
            }
        ) {
            when (notifyMeUseCase.isEventNotified(event.id)) {
                true -> {
                    notificationRepository.cancelNotification(event.id, EVENT.value)
                    notifyMeUseCase.removeNotifiedEventId(event.id)
                }
                false -> {
                    notificationRepository.saveNotification(event.id, EVENT.value)
                    notifyMeUseCase.addNotifiedEventId(event.id)
                }
            }
            sendViewEvent(NotifyMeCompleted)
        }
    }
    companion object {
        const val EVENT_DATA_PAGE_SIZE: Int = 30
        const val PREFETCH_DISTANCE = 1
    }
}

EventPagingAdapter

class EventPagingAdapter(
    private val eventViewType: ViewType
) : PagingDataAdapter<Event, RecyclerView.ViewHolder>(EventDiffCallback) {

    var clickListener: ((Event) -> Unit)? = null
    var onNotifyClicked: ((Event) -> Unit)? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (eventViewType) {
            ViewType.REGULAR_SMALL -> SmallRegularViewHolder(
                ViewEventSmallBinding.inflate(inflater, parent, false),
                onNotifyClicked
            )

            ViewType.REGULAR_SMALL_FULL_WIDTH -> SmallRegularFullWidthViewHolder(
                ViewEventSmallFullWidthBinding.inflate(inflater, parent, false),
                onNotifyClicked
            )

            ViewType.REGULAR_LARGE -> LargeRegularViewHolder(
                ViewEventLargeBinding.inflate(inflater, parent, false),
                onNotifyClicked
            )

            ViewType.LIVE_SMALL -> SmallLiveViewHolder(
                ViewEventLiveSmallBinding.inflate(inflater, parent, false),
                onNotifyClicked
            )

            ViewType.LIVE_LARGE -> LargeLiveViewHolder(
                ViewEventLiveLargeBinding.inflate(inflater, parent, false),
                onNotifyClicked
            )
            ViewType.PRODUCT_DETAIL -> ProductDetailViewHolder(
                ViewEventProductDetailBinding.inflate(inflater, parent, false),
                onNotifyClicked
            )
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position) ?: return
        when (holder) {
            is SmallRegularViewHolder -> {
                holder.bind(item)
            }

            is SmallRegularFullWidthViewHolder -> {
                holder.bind(item)
            }

            is SmallLiveViewHolder -> {
                holder.bind(item)
            }

            is LargeRegularViewHolder -> {
                holder.bind(item)
            }

            is LargeLiveViewHolder -> {
                holder.bind(item)
            }
            is ProductDetailViewHolder -> {
                holder.bind(item)
            }
        }

        holder.itemView.setOnClickListener {
            clickListener?.invoke(item)
        }
    }
}

BroadcasterEventsPagingSource

class BroadcasterEventsPagingSource(
    private val broadcasterRepository: IBroadcasterRepository,
    private val user: User,
    private val fetchStream : Boolean
) : PagingSource<Int, Event>() {

    override fun getRefreshKey(
        state: PagingState<Int, Event>
    ) = state.anchorPosition

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Event> {
        return try {
            var page = params.key ?: 1
            Log.d("page error","page: "+page)
            if (page == 0)
                page = 1

            val result = if(fetchStream) {
                 broadcasterRepository.getBroadcasterEvents(
                    user.id,
                    page
                )
            }else {
                broadcasterRepository.getBroadcasterVideos(
                    user.id,
                    page
                )
            }
            LoadResult.Page(
                data = result.data,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (result.totalCount > page * result.size) {
                    page + 1
                } else {
                    null
                }
            )
        } catch (exception: Exception) {
            LoadResult.Error(exception)
        }
    }
}

fragment_broadcaster_events.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.clickme.clickmelive.features.broadcaster.events.BroadcasterEventsViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:id="@+id/swipe_refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="@dimen/margin_16dp">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:overScrollMode="never"
                android:scrollbars="none"
                tools:itemCount="3"
                tools:listitem="@layout/view_event_small" />

        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

        <com.clickme.clickmelive.widget.NoContentLayout
            android:id="@+id/layout_no_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

dependencies:

 const val RECYCLER_VIEW = "androidx.recyclerview:recyclerview:1.2.1"
    const val PAGING = "androidx.paging:paging-runtime:3.0.0"
    const val SWIPE_REFRESH = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
0

There are 0 answers