androidkotlinandroid-roomandroid-paging-3

Scroll down the RecyclerView to the last PagingDataAdapter item in Paging3


I am developing a chat screen, all messages are immediately saved in Room and then I receive them via Paging3.

My problem is that I can't scroll down to the last aitem after sending a message. When I try to use recyclerView.smoothScrollToPosition(lastPosition) the list scrolls down to the last item in the Paging3 page.

Adapter initialization

linearLayoutManager.reverseLayout = true
binding.messageRecycler.layoutManager = linearLayoutManager
binding.messageRecycler.adapter = messagePagingAdapter

binding.layoutQuickMessages.visible = false
viewModel.readMessages(sessionId).observe(viewLifecycleOwner) { data ->
    messagePagingAdapter.submitData(viewLifecycleOwner.lifecycle, data)

Adapter

class MessageListPagingAdapter(
    private val onRichMessageStart: (Message.ExerciseType) -> Unit,
) : PagingDataAdapter<Message, RecyclerView.ViewHolder>(MessageDiffUtilItemCallback()) {
    private lateinit var mRecyclerView: RecyclerView

    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int,
    ) {
        val item = getItem(position)
        if (item != null) {
            when (item.type) {
                Message.Type.RICH -> {
                    if (holder is MessageListAdapter.RichMessageViewHolder) {
                        holder.bind(item)
                    }
                }

                Message.Type.RECEIVED -> {
                    if (holder is MessageListAdapter.ReceivedMessageViewHolder) {
                        holder.bind(item)
                    }
                }

                Message.Type.SENT -> {
                    if (holder is MessageListAdapter.SentMessageViewHolder) {
                        holder.bind(item)
                    }
                }
            }
        }
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int,
    ): RecyclerView.ViewHolder =
        when (viewType) {
            Message.Type.RECEIVED.ordinal -> {
                val binding =
                    ItemMessageReceivedBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false,
                    )
                MessageListAdapter.ReceivedMessageViewHolder(binding)
            }

            Message.Type.SENT.ordinal -> {
                val binding =
                    ItemMessageSentBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false,
                    )
                MessageListAdapter.SentMessageViewHolder(binding)
            }

            Message.Type.RICH.ordinal -> {
                val binding =
                    ItemRichMessageBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false,
                    )
                MessageListAdapter.RichMessageViewHolder(binding, onRichMessageStart)
            }

            else -> {
                val binding =
                    ItemMessageSentBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false,
                    )
                MessageListAdapter.SentMessageViewHolder(binding)
            }
        }

    override fun getItemViewType(position: Int): Int =
        when (getItem(position)?.type) {
            Message.Type.RECEIVED -> Message.Type.RECEIVED.ordinal
            Message.Type.SENT -> Message.Type.SENT.ordinal
            else -> Message.Type.RICH.ordinal
        }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        mRecyclerView = recyclerView
    }
}

ViewModel

fun readMessages(sessionId: String): LiveData<PagingData<Message>> =
    repository.readMessages(sessionId).asLiveData().cachedIn(viewModelScope)

Repository

fun readMessages(sessionId: String): Flow<PagingData<Message>> {
    val pageConfig = PagingConfig(
        pageSize = 20,
        enablePlaceholders = false,
        prefetchDistance = 10,
    )
    return Pager(
        config = pageConfig,
        pagingSourceFactory = { SelfTestHistoryPagingSource(sessionId, therapyDao) },
    ).flow.flowOn(Dispatchers.IO)
}

PagingSource

class SelfTestHistoryPagingSource(
    private val sessionId: String,
    private val dao: TherapyDao,
) : PagingSource<Int, Message>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Message> {
        val position = params.key ?: 0

        return try {
            val response = dao.readMessages(sessionId, 20, position * params.loadSize)

            LoadResult.Page(
                data = response,
                prevKey = if (position == 0) null else position - 1,
                nextKey = if (response.isEmpty()) null else position + 1,
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, Message>): Int? =
        state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
}

I couldn't find the right information.


Solution

  • Resolve

           messagePagingAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
          override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            super.onItemRangeInserted(positionStart, itemCount)
            if (linearLayoutManager.reverseLayout) {
              binding.messageRecycler.scrollToPosition(0)
            } else {
              binding.messageRecycler.scrollToPosition(messagePagingAdapter.itemCount - 1)
            }
          }
        })