Android Paging 3 – experiencing flickers, glitches or jumps in position when scrolling & loading new pages

Hello Guys im using Android Jetpack Paging library 3, I’m creating a news app that implements network + database scenario, and im following the codelab by google , im doing it almost like in the codelab i almost matched all the operations shown in the examples

It works almost as it should…but my backend response is page keyed, i mean response comes with the list of news and the next page url, remote mediator fetches the data, populates the database, repository is set, viewmodel is set…

The problem is : when recyclerview loads the data , following happens:recyclerview flickers, items jump, are removed , added again and so on. I dont know why recyclerview or its itemanimator behaves like that , that looks so ugly and glitchy. More than that, when i scroll to the end of the list new items are fetched and that glitchy and jumping effect is happening again.

I would be very grateful if you could help me, im sitting on it for three days , thank you very much in advance.Here are my code snippets:

@Entity(tableName = "blogs")
data class Blog(
@PrimaryKey(autoGenerate = true)
val databaseid:Int,

val id: Int,
val title: String,

val image: String,

val date: String,

val shareLink: String,


val status: Int,

val url: String
) {
var categoryId: Int? = null
var tagId: Int? = null

Here’s the DAO

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(blogs: List<Blog>)

 @Query("DELETE FROM blogs")
suspend fun deleteAllBlogs()

 @Query("SELECT * FROM blogs WHERE categoryId= :categoryId ORDER BY id DESC")
fun getBlogsSourceUniversal(categoryId:Int?): PagingSource<Int, Blog>

 @Query("SELECT * FROM blogs WHERE categoryId= :categoryId AND tagId= :tagId ORDER BY id DESC")
fun getBlogsSourceUniversalWithTags(categoryId:Int?,tagId:Int?): PagingSource<Int, Blog>


abstract class NewsDatabaseKt : RoomDatabase() {

abstract fun articleDAOKt(): ArticleDAOKt
abstract fun remoteKeyDao(): RemoteKeyDao

companion object {

    private var INSTANCE: NewsDatabaseKt? = null

    fun getDatabase(context: Context): NewsDatabaseKt =
        INSTANCE ?: synchronized(this) {
            INSTANCE ?: buildDatabase(context).also { INSTANCE = it }

    private fun buildDatabase(context: Context) = 


   class BlogsRemoteMediator(private val categoryId: Int,
                      private val service: NewsAPIInterfaceKt,
                      private val newsDatabase: NewsDatabaseKt,
                      private val tagId : Int? = null ,
                      private val initialPage:Int = 1
    ) : RemoteMediator<Int, Blog>() {

override suspend fun initialize(): InitializeAction {
    return InitializeAction.LAUNCH_INITIAL_REFRESH

override suspend fun load(loadType: LoadType, state: PagingState<Int, Blog>): MediatorResult {
    try {
        val page = when (loadType) {
            REFRESH ->{ 
            PREPEND -> {
                return MediatorResult.Success(endOfPaginationReached = true)}
            APPEND -> {
                val remoteKey = newsDatabase.withTransaction {
                if(remoteKey.nextPageKey == null){
                    return MediatorResult.Success(endOfPaginationReached = true)


        val apiResponse =
                if(tagId == null) {
            service.getCategoryResponsePage(RU, categoryId, page.toString())
        val blogs = apiResponse.blogs
        val endOfPaginationReached = blogs.size < state.config.pageSize

        newsDatabase.withTransaction {
            // clear all tables in the database
            if (loadType == LoadType.REFRESH) {
                if(tagId == null) {
                }else {

   {blog ->
                blog.categoryId = categoryId
                if(tagId != null) {
                    blog.tagId = tagId


        return MediatorResult.Success(
                endOfPaginationReached = endOfPaginationReached
    } catch (exception: IOException) {
        return MediatorResult.Error(exception)
    } catch (exception: HttpException) {
        return MediatorResult.Error(exception)



 class PagingRepository(
    private val service: NewsAPIInterfaceKt,
    private val databaseKt: NewsDatabaseKt
 fun getBlogsResultStreamUniversal(int: Int, tagId : Int? = null) : Flow<PagingData<Blog>>{
    val pagingSourceFactory =  {
        if(tagId == null) {

        }else databaseKt.articleDAOKt().getBlogsSourceUniversalWithTags(int,tagId)
    return Pager(
            config = PagingConfig(
                    pageSize = 1
            ,remoteMediator = 
            BlogsRemoteMediator(int, service, databaseKt,tagId)
            ,pagingSourceFactory = pagingSourceFactory


class BlogsViewModel(private val repository: PagingRepository):ViewModel(){

private var currentResultUiModel: Flow<PagingData<UiModel.BlogModel>>? = null
private var categoryId:Int?=null

fun getBlogsUniversalWithUiModel(int: Int, tagId : Int? = null): 
Flow<PagingData<UiModel.BlogModel>> {

    val lastResult = currentResultUiModel

    if(lastResult != null && int == categoryId){
        return lastResult

    val newResult: Flow<PagingData<UiModel.BlogModel>> = 
     repository.getBlogsResultStreamUniversal(int, tagId)
            .map { pagingData -> { UiModel.BlogModel(it)}}

    currentResultUiModel = newResult
    categoryId = int
    return newResult

sealed class UiModel{
    data class BlogModel(val blog: Blog) : UiModel()


   class PoliticsFragmentKotlin : Fragment() {

     private lateinit var recyclerView: RecyclerView
     private lateinit var pagedBlogsAdapter:BlogsAdapter

     lateinit var viewModelKt: BlogsViewModel
     lateinit var viewModel:NewsViewModel

     private var searchJob: Job? = null

     private fun loadData(categoryId:Int, tagId : Int? = null) {

    searchJob = lifecycleScope.launch {

        viewModelKt.getBlogsUniversalWithUiModel(categoryId, tagId).collectLatest {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.fragment_blogs, container, false)   
      viewModelKt = ViewModelProvider(requireActivity(),Injection.provideViewModelFactory(requireContext())).get(

  viewModel = ViewModelProvider(requireActivity()).get(
 pagedBlogsAdapter = BlogsAdapter(context,viewModel)
  val decoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
   recyclerView = view.findViewById(

 return view

       private fun initLoad() {
    lifecycleScope.launchWhenCreated {
        Log.d("meylis", "lqunched loadstate scope")
                // Only emit when REFRESH LoadState for RemoteMediator changes.
                .distinctUntilChangedBy { it.refresh }
                // Only react to cases where Remote REFRESH completes i.e., NotLoading.
                .filter { it.refresh is LoadState.NotLoading }
                .collect { recyclerView.scrollToPosition(0) }

  private fun initAdapter() {
    recyclerView.adapter = pagedBlogsAdapter.withLoadStateHeaderAndFooter(
            header = BlogsLoadStateAdapter { pagedBlogsAdapter.retry() },
            footer = BlogsLoadStateAdapter { pagedBlogsAdapter.retry() }

    lifecycleScope.launchWhenCreated {
        pagedBlogsAdapter.loadStateFlow.collectLatest {
            swipeRefreshLayout.isRefreshing = it.refresh is LoadState.Loading

       pagedBlogsAdapter.addLoadStateListener { loadState ->
        // Only show the list if refresh succeeds.
        recyclerView.isVisible = loadState.source.refresh is LoadState.NotLoading
                // Show loading spinner during initial load or refresh.
        progressBar.isVisible = loadState.source.refresh is LoadState.Loading
        // Show the retry state if initial load or refresh fails.
        retryButton.isVisible = loadState.source.refresh is LoadState.Error

        // Toast on any error, regardless of whether it came from RemoteMediator or PagingSource
        val errorState = loadState.source.append as? LoadState.Error
                ?: loadState.source.prepend as? LoadState.Error
                ?: loadState.append as? LoadState.Error
                ?: loadState.prepend as? LoadState.Error
        errorState?.let {
            Toast.makeText(context, "uD83DuDE28 Wooops ${it.error}", Toast.LENGTH_LONG

     companion object {

    fun newInstance(categoryId: Int, tags : ArrayList<Tag>): PoliticsFragmentKotlin {
        val args = Bundle()
        args.putInt(URL, categoryId)
        val fragmentKotlin = PoliticsFragmentKotlin()
        fragmentKotlin.arguments = args
        Log.d("meylis", "created instance")
        return fragmentKotlin


class BlogsAdapter(var context: Context?, var newsViewModel:NewsViewModel) : 
  PagingDataAdapter<BlogsViewModel.UiModel.BlogModel, RecyclerView.ViewHolder> 

private val VIEW = 10

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return when (viewType) {
        VIEW -> MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.card_layout, parent, false))

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
   val uiModel = getItem(position)
    if(uiModel == null){
        if(uiModel is BlogsViewModel.UiModel.BlogModel){(holder as MyViewHolder).bind(null)}
        if(uiModel is BlogsViewModel.UiModel.BlogModel){(holder as 


override fun getItemViewType(position: Int): Int  {
    return VIEW

companion object {
    private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<BlogsViewModel.UiModel.BlogModel>() {
        override fun areItemsTheSame(oldItem: BlogsViewModel.UiModel.BlogModel, newItem: BlogsViewModel.UiModel.BlogModel): Boolean =
        override fun areContentsTheSame(oldItem: BlogsViewModel.UiModel.BlogModel, newItem: BlogsViewModel.UiModel.BlogModel): Boolean =
                oldItem == newItem



class MyViewHolder(var container: View) : RecyclerView.ViewHolder(container) {
var cv: CardView
var mArticle: TextView
var date: TextView? = null
var time: TextView
var articleImg: ImageView
var shareView: View
var button: MaterialButton? = null
var checkBox: CheckBox

var progressBar: ProgressBar

private var blog:Blog? = null

init {
    cv = container.findViewById<View>( as CardView
    mArticle = container.findViewById<View>( as TextView
    articleImg = container.findViewById<View>( as ImageView
    //button = (MaterialButton) itemView.findViewById(;
    checkBox = container.findViewById<View>( as CheckBox
    time = container.findViewById(
    shareView = container.findViewById(
    progressBar = container.findViewById(

fun bind(blog: Blog?){
    if(blog == null){
        mArticle.text = "loading"
        time.text = "loading"
        articleImg.visibility = View.GONE
    }else { = blog
        mArticle.text = blog.title
        time.text =

        if (blog.image.startsWith("http")) {
            articleImg.visibility = View.VISIBLE
            val options: RequestOptions = RequestOptions()

                    progressBar).load(blog.image, options)
        } else {
            articleImg.visibility = View.GONE



interface NewsAPIInterfaceKt {

suspend fun getCategoryResponsePage(@Header("Language") language: String, @Query("category") 
categoryId: Int, @Query("page") pageNumber: String): BlogsResponse

suspend fun getCategoryTagResponsePage(@Header("Language") language: String, 
@Query("category") categoryId: Int,@Query("tag") tagId:Int, @Query("page") pageNumber: String)

     companion object {

    fun create(): NewsAPIInterfaceKt {
        val logger = HttpLoggingInterceptor()
        logger.level = HttpLoggingInterceptor.Level.BASIC

        val okHttpClient = UnsafeOkHttpClient.getUnsafeOkHttpClient()

        return Retrofit.Builder()


I have tried setting initialLoadSize = 1 But the problem still persists

EDIT: Thanks for your answer @dlam , yes, it does , my network API returns the list of results ordered by id. BTW, items do this jump when the application is run offline as well.

Videos when refreshing and loading online

online loading and paging

online loading and paging(2)

Videos when refreshing and loading offline

offline loading and refreshing

Thanks again, here is my gist link

EDIT Thanks a lot to @dlam, when I set pageSize=10, jumping has disappeared…Then i remembered why i set pageSize=1 in the first place… when i refresh , 3 x pageSize of items are loaded, even if i overrided initialLoadSize = 10 , it still loads 3 x pageSize calling append 2x times after refresh , what could i be doing wrong, what’s the correct way to only load first page when i refresh ?


Just following up here from comments:

Setting pageSize = 10 fixes the issue.

The issue was with pageSize being too small, resulting in PagingSource refreshes loading pages that did not cover the viewport. Since source refresh replaces the list and goes through DiffUtil, you need to provide an initialLoadSize that is large enough so that there is some overlap (otherwise scroll position will be lost).

BTW – Paging loads additional data automatically based on PagingConfig.prefetchDistance. If RecyclerView binds items close enough to the edge of the list, it will automatically trigger APPEND / PREPEND loads. This is why the default of initialLoadSize is 3 * pageSize, but if you’re still experiencing additional loads, I would suggest either adjusting prefetchDistance, or increasing initialLoadSize further.