Reputation: 385
I'm using a RecyclerView to display messages from a user and a bot, but after about 10 messages, upon scrolling, the RecyclerView's items display out of order.
I removed setStackFromEnd()
and setReverseLayout()
from the LinearLayoutManager my code in case that caused any issue, but that didn't solve the problem. I also added the line holder.setIsRecyclable(false);
, and although that worked, I didn't want to use that solution since it defeats the purpose of a RecyclerView.
Gets user response, adds the response to the list of messages, generates the reply, and calls calls updateUI()
:
public void respond(String input) {
int position = getMessagePosition();
Log.d(CHAT_FRAGMENT_TAG, "Received Message: " + input);
String reply = mBot.reply("user", input);
Log.d(CHAT_FRAGMENT_TAG, "Bot Reply: " + reply);
mMessages.add(new Message(reply, Message.BOT_MESSAGE, messagePosition()));
updateUI(position);
Log.d(CHAT_FRAGMENT_TAG, "mMessages List: " + mMessages.get(position).getText());
}
updateUI()
:
public void updateUI(int insertPosition) {
if (mAdapter == null) {
mAdapter = new MessageAdapter();
mRecyclerView.setAdapter(mAdapter);
} else
mAdapter.notifyItemInserted(insertPosition);
}
onBindViewHolder()
:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Message message = mMessages.get(position);
if (getItemViewType(position) == Message.USER_MESSAGE)
((UserMessageHolder) holder).bind(message);
else
((BotMessageHolder) holder).bind(message);
}
bind()
:
public void bind(Message message) {
mBotTextView.setText(message.getText());
}
messagePosition()
and getMessagePosition()
:
public static int messagePosition() {
//A convenience function to get the current position and increment it as well for the next array element.
int returnValue = mMessagePosition;
mMessagePosition++;
return returnValue;
}
public static int getMessagePosition() {
return mMessagePosition;
}
There is an onClick()
method on an EditText that takes the user's response and calls the respond()
method. The respond()
method gets the bot's reply, and it calls the updateUI()
method to let the adapter know that the array is larger since new messages were added (one from the user and one from the bot). This process works fine until there are about 10 messages and the user needs to scroll. From that point, the messages reload on the screen incorrectly by repeating themselves or not loading in their correct spot.
Upvotes: 0
Views: 1490
Reputation: 336
I have had the same issue. Consider storing all the conversation somewhere, for example I do it inside of Conversation class
//List adapter
class MessagesListAdapter : RecyclerView.Adapter<MessageViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder = MessageViewHolder(LayoutInflater.from(parent.context).inflate(viewType, parent, false))
override fun getItemCount(): Int = Conversation.getCount()
override fun reloadDataSet() {}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
holder.bind(Conversation.getMessage(position))
}
override fun getItemViewType(position: Int): Int = when (Conversation.getMessage(position).response) {
true -> R.layout.message_response
false -> R.layout.message_sent
}
}
//Message Holder
class MessageViewHolder(private var item: View) : RecyclerView.ViewHolder(item) {
fun bind(message: TextMessage) {
if (message.response) {
item.findViewById<TextView>(R.id.tv_response_bubble).text = message.text
} else {
item.findViewById<TextView>(R.id.tv_user_bubble).text = message.text
}
}
}
//Activity class
class ChatActivity : AppCompatActivity() {
private val listAdapter = MessagesListAdapter()
private lateinit var messagesListView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
messagesListView = findViewById(R.id.recyclerView)
messagesListView.adapter = listAdapter
val layoutManager = LinearLayoutManager(this)
//For messages to appear from bottom to top
layoutManager.stackFromEnd = true
messagesListView.layoutManager = layoutManager
}
//Code you call from activity to update the list
private fun addMessage(message: TextMessage) {
if (!message.isNullOrBlank()) {
Conversation.add(message)
listAdapter.notifyItemChanged(Conversation.getCount() - 1)
messagesListView.smoothScrollToPosition(Conversation.getCount() - 1)
}
}
}
//Conversation class
public class Conversation {
private static List<TextMessage> currentConversation;
public static void clear() {
currentConversation = new ArrayList<>();
}
public static void add(final TextMessage message) {
if (currentConversation == null) {
clear();
}
currentConversation.add(message);
}
static TextMessage getMessage(final int pos) {
if (currentConversation == null) {
return null;
}
return currentConversation.get(pos);
}
static int getCount() {
return currentConversation == null ? 0 : currentConversation.size();
}
}
Upvotes: 3
Reputation: 54214
If you have problems with messages appearing in the wrong spot, or not appearing at all, the underlying problem is that your data set doesn't have the data your adapter thinks it should.
Imagine that you have a list of strings ["A", "B", "C"]
. You add two messages to it, but you only notify the adapter that you've added one. The adapter thinks the list is now ["A", "B", "C", "D"]
, but it is actually ["A", "B", "C", "D", "E"]
. This will eventually lead to problems.
In the code you've posted, you only have a single notifyItemInserted()
call (for the bot's response to the user's message). But you've described a workflow that includes adding both a user message and a bot reply. That makes me think that you've missed a notify
call you need.
You can replace all calls to notifyItemInserted()
with notifyDataSetChanged()
and see if this fixes the problem. In general, it is better to use notifyItemInserted()
, but if this fixes the problem then you'll know that the problem is that you've missed a notify
call you needed. Then you can go through the code very carefully and make sure that any time mMessages
is changed you notify the adapter.
Upvotes: 1