Is ArrayAdapter thread safe in android? If not, what can I do to make it thread safe?

Gallal picture Gallal · Jun 5, 2011 · Viewed 10k times · Source

Lets say I extend ArrayAdapter and in the code where I am overriding getView(int i, View v, ViewGroup g), I retrieve the current item using getItem(i). Can I be sure that getItem(i) will return an item even if other threads manipulate the same ArrayAdapter?

I am not sure, but I think the answer is no. If it is, what do you suggest I do to make it thread-safe?

Answer

hackbod picture hackbod · Jun 5, 2011

It's not a matter of ArrayAdapter being thread safe. ListView and other such UI widgets that work with an Adapter do not allow the contents of the adapter to change unexpectedly on them. And this is more than just due to other threads -- you need to tell the ListView about the change you make before it next tries to interact with your adapter.

If you allow another thread to modify the adapter, or modify it on the main thread but don't tell ListView about the change before allowing anything else to happen, you will randomly (due to races) get exceptions thrown by ListView about the adapter changing unexpectedly.

In the specific case of ArrayAdapter if you use the API to modify its contents it will take care of telling the list view about the change. However you must make such changes on the main thread, to make sure that the list view doesn't try to access the adapter between the point where your change is made and the list view is told about that change.

If you are only making simple changes to ArrayAdapter (adding and removing a few items), then you will be fine but you must do these on the main thread.

For more significant changes (such as say the adapter getting a new data set due to a fetch of new data from a server), Consider not using ArrayAdapter and instead implementing your own subclass of BaseAdapter. ArrayAdapter is intended for situations where you have a small simple fairly static set of data to show -- simple situations. For more complicated things, you'll probably be happier just implementing BaseAdapter and doing the data management yourself.

The typical way an adapter updates in these complicated situations is that a background thread generates the new data set, and once that is available then on the main thread it is swapped in to the adapter atomically with a call to notifyDataSetChanged() to let the ListView know that the data has changed.

So let's say you are showing some data that is an array of MyItem objects. Keep your data in a play array:

ArrayList<MyItem>

Implement a subclass of BaseAdapter that shows this list:

class MyAdapter extends BaseAdapter<MyItem> {
    ArrayList<MyItem> mItems;

    public int getCount() {
        return mItems != null ? mItems.size() : 0;
    }

    public MyItem getItem(int position) {
        return mItems.get(i);
    }

    ...
}

And now here is a function you could implement on the adapter that can be called from another thread to provide a new data set to be shown:

    final Handler mHandler = new Handler();

    void setDataFromAnyThread(final ArrayList<MyItem> newData) {
        // Enqueue work on mHandler to change the data on
        // the main thread.
        mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mItems = newData;
                    notifyDataSetChanged();
                }
            });
    }

Of course if you are using AsyncTask to do your data generation this already has a convenient facility for performing that work back on the main thread. Likewise you could use the new Loader facility to take care of the generation in the background.

And if you still want to use ArrayAdapter, in your function above you could do this by clearing the current data and adding the new data to the now empty adapter. This is just more overhead that doesn't really gain you anything.