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.
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)
}
}
})