Brian
Brian

Reputation: 1989

Why Is My Custom Android ArrayAdapter Not Refreshing Its ListView When New Data Is Added To The Array?

As part of my Android app, I am integrating a very simple custom group chat feature; this is just a proof-of-concept feature. I am aware of the fact that this is transient, and that my chats disappear whenever I restart my app. My app uses Fragments and a ViewPager to create tabs within my app.

What I have backing this feature is a LinkedList which holds Message objects (send time, sender name, message). Whenever a chat is sent or received, it is added to the LinkedList. At the start of my MainActivity.java, I declare the list

public static LinkedList<Message> chatList;

In the onCreate method, I initialize the list

chatList = new LinkedList<Message>();

I have a listener thread that runs in the background that listens for incoming messages. I passed the chatList into this thread in its constructor

MyListener myListener = new MyListener(MainActivity.this, chatList);
Thread listenerThread = new Thread(myListener);
listenerThread.start();

Whenever the listener thread receives a message, it sticks it into the LinkedList

chatList.add(new_message_object);

To display the chats, I use a ListView that is backed by a custom ArrayAdapter, in which I have overridden the getView() method. The ArrayAdapter gets the array from the toArray() method of my LinkedList and displays the chats on the screen.

This process almost works. Whenever chats are received, the LinkedList is successfully populated. The problem is getting the ListView to immediately update and display the chats. If I switch to a new fragment/tab within my app, then switch back to my chat tab, then the list is populated; but, I have to do this whenever I want to see any new chats.

This is also true for any chats I enter locally (i.e., within my chat feature). It is still added to the LinkedList, but the ListView does not refresh.

I don't want to use a SwipeRefreshLayout, I would rather the list update itself. I created a method within the custom ArrayAdapter

public void refreshList(){
    this.notifyDataSetChanged();
}

I call this whenever a new message object is added to the LinkedList, but it is not refreshing the list.

So, what am I doing incorrectly? Like I said, I would prefer the ListView update itself like a proper chat program does.

Thanks

EDIT:

As requested, here is my custom ArrayAdapter

public class CustomArrayAdapter extends ArrayAdapter {

    Context context;
    int resource;
    Object[] objects;

    public CustomArrayAdapter(Context context, int resource, Object[] objects) {
        super(context, resource, objects);
        this.context = context;
        this.resource = resource;
        this.objects = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        MessageHolder messageHolder = null;

        if(row == null){
            LayoutInflater inflater = ((Activity)context).getLayoutInflater();
            row = inflater.inflate(resource, parent, false);
            messageHolder = new MessageHolder();
            messageHolder.chat_information = (TextView) row.findViewById(R.id.chat_information);
            messageHolder.chat_message = (TextView) row.findViewById(R.id.chat_message);

            row.setTag(messageHolder);
        } else {
            messageHolder = (MessageHolder) row.getTag();
        }

        Message item = (Message) objects[position];
        messageHolder.chat_information.setText(item.getSenderName() + Constants.NEWLINE + item.getSendTime());
        messageHolder.chat_message.setText(item.getMessageText());

        return row;
    }

    public void refreshList(){
        this.notifyDataSetChanged();
    }

    private class MessageHolder {
        TextView chat_information;
        TextView chat_message;
    }
}

EDIT 2:

I am adding my Chat.java fragment.

public class Chat extends Fragment {

    private View rootView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.chat, container, false);
        return rootView;
    }

    @Override
    public void onViewCreated(View rootView, Bundle savedInstanceState) {
        super.onViewCreated(rootView, savedInstanceState);
        displayChats();
    }

    @Override
    public View getView() {
        Button sendChatButton = (Button) rootView.findViewById(R.id.text_send);
        sendChatButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Date rightNow = new Date();
                SimpleDateFormat timeSDF = new SimpleDateFormat(Constants.SIMPLE_TIME);
                SimpleDateFormat dateSDF = new SimpleDateFormat(Constants.SIMPLE_DATE);
                SharedPreferences myAppPreferences = getContext().getSharedPreferences(Constants.PREFS_NAME, getContext().MODE_PRIVATE);
                EditText chatEntryWindow = (EditText)rootView.findViewById(R.id.chat_text_compose);
                String message = chatEntryWindow.getText().toString();
                String username = myAppPreferences.getString("username", Constants.TABLET_ID);
                Message myMessage = new Message(username, message, 0, dateSDF.format(rightNow), timeSDF.format(rightNow));
                CustomArrayAdapter caa = new CustomArrayAdapter(getActivity(), R.layout.outgoing_line_of_chat, MainActivity.chatList);
                caa.add(myMessage);
                caa.notifyDataSetChanged();
                new SendChat(getActivity(), message, username).execute();
            }
        });
        return super.getView();
    }

    public void displayChats(){
        ListView list = (ListView) rootView.findViewById(R.id.chat_text_display);
        ArrayAdapter adapter = new CustomArrayAdapter(getActivity(), R.layout.outgoing_line_of_chat, MainActivity.chatList);
        list.setAdapter(adapter);
    }
}

Upvotes: 0

Views: 1967

Answers (3)

Krish
Krish

Reputation: 3885

Change the code like this . This will not make too many changes ,

public class CustomArrayAdapter extends ArrayAdapter {

    Context context;
    int resource;
    List objects ;
    Activity activity ;

    public CustomArrayAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
        this.context = context;
        this.resource = resource;
        //clone the arraylist.
        this.objects = new ArrayList(objects);
        activity = (Activity) context;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        MessageHolder messageHolder = null;

        if (row == null) {
            LayoutInflater inflater = LayoutInflater.from(context);
            row = inflater.inflate(resource, parent, false);
            messageHolder = new MessageHolder();
            messageHolder.chat_information = (TextView) row.findViewById(R.id.chat_information);
            messageHolder.chat_message = (TextView) row.findViewById(R.id.chat_message);

            row.setTag(messageHolder);
        } else {
            messageHolder = (MessageHolder) row.getTag();
        }

        Message item = (Message) objects.get(position);
        messageHolder.chat_information.setText(item.getSenderName() + Constants.NEWLINE + item.getSendTime());
        messageHolder.chat_message.setText(item.getMessageText());

        return row;
    }

    public void setObjects(List newObjects)
    {
        if (objects != null)
        {
            objects.clear();
            objects.addAll(newObjects);
        }
    }


    public void refreshList() {
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });

    }

    private class MessageHolder {
        TextView chat_information;
        TextView chat_message;
    }
}

And also one each chat message call setObjects() method with the linked list and call refreshList() method

Upvotes: 0

Bhunnu Baba
Bhunnu Baba

Reputation: 1802

use the following after inserting new data in array adapter:

yourAdapterName.notifyDataSetChanged();

Upvotes: 1

CommonsWare
CommonsWare

Reputation: 1006664

Your CustomArrayAdapter does not use your LinkedList. It uses an Object[]. I assume, from your question and comments, that when you create your CustomArrayAdapter, at that point, you call toArray() on the LinkedList to get your Object[].

At this point, though, the Object[] is completely decoupled from the LinkedList. Suppose, at the outset, the LinkedList has 5 Message objects. You call toArray(), and you get an Object[5] containing those 5 Message objects. You later call add() on the LinkedList, to add a 6th Message. The Object[5] is still an Object[5] and knows nothing about that 6th Message. The only way the AdapterView will update is if you replace the CustomArrayAdapter entirely.

So, get rid of the Object[]. ArrayAdapter takes a List in half of its constructors. So, replace:

public class CustomArrayAdapter extends ArrayAdapter {

    Context context;
    int resource;
    Object[] objects;

    public CustomArrayAdapter(Context context, int resource, Object[] objects) {
        super(context, resource, objects);
        this.context = context;
        this.resource = resource;
        this.objects = objects;
    }

with:

public class CustomArrayAdapter extends ArrayAdapter<Message> {

    Context context;
    int resource;

    public CustomArrayAdapter(Context context, int resource, List<Message> messages) {
        super(context, resource, messages);
        this.context = context;
        this.resource = resource;
    }

Then, in getView(), replace Message item = (Message) objects[position]; with Message item=getItem(position);.

Finally, when a new Message arrives, call add() on the CustomArrayAdapter, which will both update your List<Message> and update the attached AdapterView.

Upvotes: 0

Related Questions