0

I'm trying to build a chat app that uses a listview to display messages. I'm using SignalR for realtime communication. The issue I'm having is, the listview adapter only updates the list on the receivers end when I scroll but on the senders end, the message shows immediately. Here's the activity for that chat:

 protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ISharedPreferences pref = Application.Context.GetSharedPreferences("UserInfo", FileCreationMode.Private);
        loggeduser = pref.GetString("Username", String.Empty);



        SetContentView(Resource.Layout.chat_activity);

        initControls();
    }

    private async void initControls()
    {
        SignalRClientHelper proxySubscriber = SignalRClientHelper.GetInstance();
        await proxySubscriber.StartConnection();
        proxySubscriber.OnMessageReceived += proxySubscriber_OnMessageReceived;

        messagesContainer = FindViewById<ListView>(Resource.Id.messagesContainer);
        messageET = FindViewById<EditText>(Resource.Id.messageEdit);
        sendBtn = FindViewById<Button>(Resource.Id.chatSendButton);

        adapter = new ChatAdapter(this, new List<ChatMessage>(), loggeduser);
        messagesContainer.Adapter = adapter;

        //loadDummyHistory();

        sendBtn.Click += (o, e) =>
        {
            string messageText = messageET.Text.ToString();
            if (TextUtils.IsEmpty(messageText))
            {
                return;
            }

            proxySubscriber.InvokeSendMessage("psyoptica", messageText);

            ChatMessage chatMessage = new ChatMessage();
            chatMessage.Message = messageText;
            chatMessage.Username = loggeduser;

            messageET.Text = "";

            displayMessage(chatMessage);
        };

        RelativeLayout container = FindViewById<RelativeLayout>(Resource.Id.container);
    }

    void proxySubscriber_OnMessageReceived(string username, string message)
    {
        ChatMessage chatMessage = new ChatMessage { Username = username, Message = message };
        displayMessage(chatMessage);

    }

    public void displayMessage(ChatMessage message)
    {
        adapter.add(message);
        adapter.NotifyDataSetChanged();
        scroll();
    }

    private void scroll()
    {
        messagesContainer.SetSelection(messagesContainer.Count - 1);
    }


}

and here's the code for the listview adapter:

class ChatAdapter : BaseAdapter
{
    private List<ChatMessage> messages;
    private Activity context;
    private string loggedUsername;

    public ChatAdapter(Activity context, List<ChatMessage> messages, string loggedUsername)
    {
        this.messages = messages;
        this.context = context;
        this.loggedUsername = loggedUsername;
    }
    public override int Count
    {
        get { return messages.Count; }
    }

    public override Java.Lang.Object GetItem(int position)
    {
        return null;
    }

    public override long GetItemId(int position)
    {
        return position;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        View view;
        ViewHolder holder;

        if (convertView == null)
        {
            view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.chatMessage, parent, false);
            holder = createViewHolder(view);
        }
        else
        {
            view = convertView;
            holder = createViewHolder(view);
        }

        bool isMe = messages[position].Username == loggedUsername;

        setAlignment(holder, isMe);

        holder.txtMessage.Text = messages[position].Message;

        return view;
    }

    private void setAlignment(ViewHolder holder, bool isMe)
    {
        if (!isMe)
        {
            holder.contentWithBG.SetBackgroundResource(Resource.Drawable.in_message_bg);

            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)holder.contentWithBG.LayoutParameters;
            layoutParams.Gravity = GravityFlags.Right;
            holder.contentWithBG.LayoutParameters = layoutParams;

            RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)holder.content.LayoutParameters;
            lp.AddRule(LayoutRules.AlignParentLeft, 0);
            lp.AddRule(LayoutRules.AlignParentRight);

            holder.content.LayoutParameters = lp;

            layoutParams = (LinearLayout.LayoutParams)holder.txtMessage.LayoutParameters;
            layoutParams.Gravity = GravityFlags.Right;
            holder.txtMessage.LayoutParameters = layoutParams;
        }
        else
        {
            holder.contentWithBG.SetBackgroundResource(Resource.Drawable.out_message_bg);

            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)holder.contentWithBG.LayoutParameters;
            layoutParams.Gravity = GravityFlags.Left;
            holder.contentWithBG.LayoutParameters = layoutParams;

            RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)holder.content.LayoutParameters;
            lp.AddRule(LayoutRules.AlignParentRight, 0);
            lp.AddRule(LayoutRules.AlignParentLeft);

            holder.content.LayoutParameters = lp;

            layoutParams = (LinearLayout.LayoutParams)holder.txtMessage.LayoutParameters;
            layoutParams.Gravity = GravityFlags.Left;
            holder.txtMessage.LayoutParameters = layoutParams;
        }
    }

    public void add(ChatMessage message)
    {
        messages.Add(message);
    }

    public void add(List<ChatMessage> chatMessages)
    {
        messages.AddRange(chatMessages);
    }

    private ViewHolder createViewHolder(View v)
    {
        ViewHolder holder = new ViewHolder();
        holder.txtMessage = v.FindViewById<TextView>(Resource.Id.txtMessage);
        holder.content = v.FindViewById<LinearLayout>(Resource.Id.content);
        holder.contentWithBG = v.FindViewById<LinearLayout>(Resource.Id.contentWithBackground);
        holder.txtInfo = v.FindViewById<TextView>(Resource.Id.txtInfo);
        return holder;
    }

    private class ViewHolder
    {
        public TextView txtMessage { get; set; }
        public TextView txtInfo { get; set; }
        public LinearLayout content { get; set; }
        public LinearLayout contentWithBG { get; set; }

    }

}

The method displayMessage() is called each time there's a new message added to the list. I don't understand why I have to scroll for the change to show on the receivers end. Can anyone point out the error in my code?

Ahmed Mujtaba
  • 2,110
  • 5
  • 36
  • 67
  • I don't know if i understand the question, you are updating the list adding the element to last position of list view, that's why you will need to scroll to see it. – AmirG Jun 15 '16 at 20:48
  • Yes but shouldn't "messagesContainer.SetSelection(messagesContainer.Count - 1);" automatically scroll to show the last item on the list? besides, the list item doesn't appear even when the list is empty and I have to scroll to show the newly added item. – Ahmed Mujtaba Jun 15 '16 at 20:51
  • check my answer please – AmirG Jun 15 '16 at 20:59

1 Answers1

2

Try with scrollToPosition() instead of setSelection() and in the add() method in the adapter notifyDataSetChanged();

On your class ChatAdapter:

public void add(ChatMessage message)
{ 
    messages.Add(message);
    NotifyDataSetChanged();
}

public void add(List<ChatMessage> chatMessages)
{
    messages.AddRange(chatMessages);
    NotifyDataSetChanged();
}

Try running the process inside the proxySubscriber_OnMessageReceived():

 void proxySubscriber_OnMessageReceived(string username, string message)
{
   runOnUiThread(new Runnable() {
    @Override
    public void run() {
     ChatMessage chatMessage = new ChatMessage { Username = username, Message = message };
     displayMessage(chatMessage);
    }
   });


}
AmirG
  • 615
  • 8
  • 18
  • Didn't get the second part of your answer. I should do "notifyDataSetChanged()" in the add() method in the adapter? – Ahmed Mujtaba Jun 15 '16 at 21:02
  • No. I don't think it's the issue with the scroll as the items should appear when there isn't the need to scroll. If you see the click event "sendBtn.Click", I'm using the same method "displayMessage()" which I'm using when there's a callback from the server but the list refreshes for the click event but not for the server side event. – Ahmed Mujtaba Jun 15 '16 at 21:40
  • This issue is similar to the issue here "http://stackoverflow.com/questions/16436542/listview-doesnt-visually-update-its-content-after-notifydatasetchanged" but I don't think that solution applies in my case. – Ahmed Mujtaba Jun 15 '16 at 21:42
  • mmm i see since it is maybe an async process you will need to run on uithread the displayMessage(); method. I updated the answer again. – AmirG Jun 15 '16 at 21:50
  • Ohh yeah that could be it. But still, when the list refreshes only when I scroll. It shouldn't reflect the changes at all if the method I'm calling is running in a separate thread. I'll try your solution anyway.. – Ahmed Mujtaba Jun 15 '16 at 21:53
  • Well depends because the adapter list can be being updated in the async process but the ui change wont ever happen, so when you scroll he reads the already updated list and draw it correctly. – AmirG Jun 15 '16 at 21:58
  • 1
    Yepp. This worked. Maybe the response from the server was running on a separate thread. Updating the list in the UI thread worked. Can't thank you enough my bro!! :) – Ahmed Mujtaba Jun 15 '16 at 22:03
  • Great dude no problem! :) – AmirG Jun 15 '16 at 22:04