0

Hello to all programmers. I try to create simple chat. The first chatter's messages should be in white square with rounded corners, the second chatter's messages should be in blue square with rounded corners and with some icon. So I decided to use listview and adapter for this task. In adapter I use simple set of data {bool type, sting message, DateTime date}. So Adapter Code is

class ChatMessagesViewHolder : Java.Lang.Object
{
    internal TextView chatMessageView;
    public void initialize(View view){}
}
public class ChatMessagesAdapter : BaseAdapter
{
    Activity _context;
    List<MessageData> _dataList;

    public ChatMessagesAdapter(Activity context, List<MessageData> dataList)
    {
        _context = context;
        _dataList = dataList;
    }
    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        ChatMessagesViewHolder chatMessagesViewHolderClass;
        View view;
        view = convertView;
        int layout = _dataList[position]._type == 0 ? Resource.Layout.MessageLitsUserItem:Resource.Layout.MessageListWorkerItem;
        Typeface openSansRegular = Typeface.CreateFromAsset(_context.Assets, "fonts/OpenSans-Regular.ttf");
        if (view == null)
        {
            view = _context.LayoutInflater.Inflate(layout, parent, false);
            chatMessagesViewHolderClass = new ChatMessagesViewHolder();
            chatMessagesViewHolderClass.chatMessageView = view.FindViewById<TextView>(Resource.Id.messageText);
            chatMessagesViewHolderClass.initialize(view);
            view.Tag = chatMessagesViewHolderClass;
        }
        else
        {
            chatMessagesViewHolderClass = (ChatMessagesViewHolder)view.Tag;
        }
        chatMessagesViewHolderClass.chatMessageView.Typeface = openSansRegular;
        chatMessagesViewHolderClass.chatMessageView.Text = _dataList[position]._date.ToString()+"\n"+ _dataList[position]._message;
        return view;
    }
    public void Add(MessageData messageData)
    {
        _dataList.Add(messageData);
    }
}

In Activity I fill Adapter with test information that way - message from 1-st chatter, message from 2-nd chatter. It repeats 10 times.

public class ChatActivity : Activity{
    ListView chatList;
    ChatMessagesAdapter chatAdapter;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.ChatLayout);
        List<MessageData> dataList = new List<MessageData>();
        for (int i = 0; i < 20; i++)
        {
            string message = "Test message from chatter "+(i%2).ToString();
            dataList.Add(new MessageData(i % 2, message, DateTime.Now));
        }
        chatAdapter = new ChatMessagesAdapter(this, dataList);
        chatList.Adapter = chatAdapter;
    }
}

When I run app (in Visual studio emulator for android) first rendering of list is correct, but when I scroll list up/down, the layouts of ListView mixes - 1-st chatter's message sometimes get 2-nd chatter's template (with blue square and icon), 2-nd chatter's message sometimes get 1-st chatter's template (with white square and no icon). For Example

Example

Can anybody help me with this issue? Any help will be appreciated.

UPDATED

New code of Adapter, updated to work with two different ViewHolders for different type of messages.

class ChatUserMessagesViewHolder : Java.Lang.Object
{
    internal RelativeLayout messageBox;
    internal TextView messageText;
    public void initialize(View view) { }
}
class ChatWorkerMessagesViewHolder : Java.Lang.Object
{
    internal RelativeLayout messageBox;
    internal TextView messageText;
    internal ImageView workerIcon;
    public void initialize(View view) { }
}
public class ChatMessagesAdapter : BaseAdapter
{
    Activity _context;
    List<MessageData> _dataList;

    public ChatMessagesAdapter(Activity context, List<MessageData> dataList)
    {
        _context = context;
        _dataList = dataList;
    }
    public override Java.Lang.Object GetItem(int position)
    {
        return position;
    }
    public override long GetItemId(int position)
    {
        return position;
    }
    public override int Count
    {
        get
        {
            return _dataList.Count;
        }
    }
    public override int GetItemViewType(int position)
    {
        return _dataList[position]._type;
    }
    public override int ViewTypeCount
    {
        get
        {
            return 2;
        }
    }
    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        ChatMessagesViewHolder chatMessagesViewHolderClass;
        ChatWorkerMessagesViewHolder chatWorkerMessagesViewHolderClass;
        ChatUserMessagesViewHolder chatUserMessagesViewHolderClass;
        View view;
        view = convertView;
        int layout = _dataList[position]._type == 0 ? Resource.Layout.MessageLitsUserItem:Resource.Layout.MessageListWorkerItem;
        Typeface openSansRegular = Typeface.CreateFromAsset(_context.Assets, "fonts/OpenSans-Regular.ttf");
        if (view == null)
        {
            switch (GetItemViewType(position))
            {
                case 0:
                    view = _context.LayoutInflater.Inflate(Resource.Layout.MessageLitsUserItem, null);
                    chatUserMessagesViewHolderClass = new ChatUserMessagesViewHolder();
                    chatUserMessagesViewHolderClass.messageBox = view.FindViewById<RelativeLayout>(Resource.Id.messageBox);
                    chatUserMessagesViewHolderClass.messageText = view.FindViewById<TextView>(Resource.Id.messageText);
                    chatUserMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatUserMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    view.Tag = chatUserMessagesViewHolderClass;
                    break;
                case 1:
                    view = _context.LayoutInflater.Inflate(Resource.Layout.MessageListWorkerItem, null);
                    chatWorkerMessagesViewHolderClass = new ChatWorkerMessagesViewHolder();
                    chatWorkerMessagesViewHolderClass.messageBox = view.FindViewById<RelativeLayout>(Resource.Id.messageBox);
                    chatWorkerMessagesViewHolderClass.messageText = view.FindViewById<TextView>(Resource.Id.messageText);
                    chatWorkerMessagesViewHolderClass.workerIcon = view.FindViewById<ImageView>(Resource.Id.workerIcon);
                    chatWorkerMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatWorkerMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    break;
            }
        }
        else
        {
            switch (GetItemViewType(position))
            {
                case 0:
                    chatUserMessagesViewHolderClass = (ChatUserMessagesViewHolder)view.Tag;
                    chatUserMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatUserMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    break;
                case 1:
                    chatWorkerMessagesViewHolderClass = (ChatWorkerMessagesViewHolder)view.Tag;
                    chatWorkerMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatWorkerMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    break;
            }
        }
        return view;
    }

    public void Add(MessageData messageData)
    {
        _dataList.Add(messageData);
    }
}

2 Answers2

1

As suggest Iulian Popescu according answer for this post solution for this issue is using two different ViewHolder and implementing GetItemViewType adapter's method . In my case that is ChatUserMessagesViewHolder for 1-st chatter's messages and ChatWorkerMessagesViewHolder for 2-nd chatter's messages. The code of solution is

class ChatUserMessagesViewHolder : Java.Lang.Object
{
    internal RelativeLayout messageBox;
    internal TextView messageText;
    public void initialize(View view) { }
}
class ChatWorkerMessagesViewHolder : Java.Lang.Object
{
    internal RelativeLayout messageBox;
    internal TextView messageText;
    internal ImageView workerIcon;
    public void initialize(View view) { }
}
public class ChatMessagesAdapter : BaseAdapter
{
    Activity _context;
    List<MessageData> _dataList;

    public ChatMessagesAdapter(Activity context, List<MessageData> dataList)
    {
        _context = context;
        _dataList = dataList;
    }
    public override Java.Lang.Object GetItem(int position)
    {
        return position;
    }
    public override long GetItemId(int position)
    {
        return position;
    }
    public override int Count
    {
        get
        {
            return _dataList.Count;
        }
    }
    public override int GetItemViewType(int position)
    {
        return _dataList[position]._type;
    }
    public override int ViewTypeCount
    {
        get
        {
            return 2;
        }
    }
    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        ChatWorkerMessagesViewHolder chatWorkerMessagesViewHolderClass;
        ChatUserMessagesViewHolder chatUserMessagesViewHolderClass;
        View view;
        view = convertView;
        int layout = _dataList[position]._type == 0 ? Resource.Layout.MessageLitsUserItem : Resource.Layout.MessageListWorkerItem;
        Typeface openSansRegular = Typeface.CreateFromAsset(_context.Assets, "fonts/OpenSans-Regular.ttf");
        if (view == null)
        {
            switch (GetItemViewType(position))
            {
                case 0:
                    view = _context.LayoutInflater.Inflate(Resource.Layout.MessageLitsUserItem, null);
                    chatUserMessagesViewHolderClass = new ChatUserMessagesViewHolder();
                    chatUserMessagesViewHolderClass.messageBox = view.FindViewById<RelativeLayout>(Resource.Id.messageBox);
                    chatUserMessagesViewHolderClass.messageText = view.FindViewById<TextView>(Resource.Id.messageText);
                    chatUserMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatUserMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    view.Tag = chatUserMessagesViewHolderClass;
                    break;
                case 1:
                    view = _context.LayoutInflater.Inflate(Resource.Layout.MessageListWorkerItem, null);
                    chatWorkerMessagesViewHolderClass = new ChatWorkerMessagesViewHolder();
                    chatWorkerMessagesViewHolderClass.messageBox = view.FindViewById<RelativeLayout>(Resource.Id.messageBox);
                    chatWorkerMessagesViewHolderClass.messageText = view.FindViewById<TextView>(Resource.Id.messageText);
                    chatWorkerMessagesViewHolderClass.workerIcon = view.FindViewById<ImageView>(Resource.Id.workerIcon);
                    chatWorkerMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatWorkerMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    view.Tag = chatWorkerMessagesViewHolderClass;
                    break;
            }
        }
        else
        {
            switch (GetItemViewType(position))
            {
                case 0:
                    chatUserMessagesViewHolderClass = (ChatUserMessagesViewHolder)view.Tag;
                    chatUserMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatUserMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    break;
                case 1:
                    chatWorkerMessagesViewHolderClass = (ChatWorkerMessagesViewHolder)view.Tag;
                    chatWorkerMessagesViewHolderClass.messageText.Typeface = openSansRegular;
                    chatWorkerMessagesViewHolderClass.messageText.Text = _dataList[position]._date.ToString() + "\n" + _dataList[position]._message;
                    break;
            }
        }
        return view;
    }

    public void Add(MessageData messageData)
    {
        _dataList.Add(messageData);
    }
}
0

This behaviour happens because of a mechanism called view recycling, which basically reuses an existing list view item if it is no longer visible (which usually happens when is it scrolled outside of the screen, hence your behaviour only when scroll is made).

 chatMessagesViewHolderClass.chatMessageView.Typeface = openSansRegular;
 chatMessagesViewHolderClass.chatMessageView.Text = _dataList[position]._date.ToString()+"\n"+ _dataList[position]._message;

In the above code you set the data for you list, but you ignore completely how the row should be generated.

To fix the problem you have to implement in your adapter getItemViewType in order to let it know that you have more than one layout. I can't help you with any C# code, but here you can find an example how to use that in Java.

Iulian Popescu
  • 2,595
  • 4
  • 23
  • 31
  • Thanks, but it doesn't help. I implemented **GetItemViewType**. It returns **_dataList[position]._type**. But even this method returns 1, **view.Tag** can return view with layout for 0 type. So I get exception, when I get holder from view.Tag. – Adeptus Mechanicus Jul 06 '17 at 08:33
  • Did you check this https://stackoverflow.com/questions/42276677/android-app-crash-when-scrolling-list-view-with-2-different-cells/42278329#42278329 ? And implemented as it is there? – Iulian Popescu Jul 06 '17 at 08:36
  • To @Iulian Popescu Yes, with little changes for C# and BaseAdapter (not ArrayAdapter). – Adeptus Mechanicus Jul 06 '17 at 08:57
  • You forgot to set the corresponding view as tag when view type is 1 as you did here: `view.Tag = chatUserMessagesViewHolderClass;` – Iulian Popescu Jul 06 '17 at 12:11
  • To @Iulian Popescu You're right, mea culpa. =) Thank you for help. If you agree, I will set this solution as answer. – Adeptus Mechanicus Jul 06 '17 at 13:47