17

I'm trying update a ListView with an entirely new ArrayList. For API 11 it works great using the addAll(...) method, but this is not available for earlier APIs. I can't figure out how to go about updating an entire list like this for older versions.

ArrayAdapter<String> textList = new ArrayAdapter<String>(listener, R.layout.list_item, stringList);
listener.setListAdapter(textList);

Then later...

textList.clear();
textList.addAll(stringList); <--- works fine for Android 3.0 (API Level 11) and beyond, but not before. 

How did you go about doing this before addAll() was introduced in API 11? Thanks.

yorkw
  • 40,926
  • 10
  • 117
  • 130
mattboy
  • 2,870
  • 5
  • 26
  • 40

8 Answers8

19

Here's the full code block that uses the native addAll() for Android devices with SDK_INT >= 11, and uses the loop workaround for devices with API level less than 11.

@TargetApi(11)
public void setData(List<String> data) {
    clear();
    if (data != null) {
        //If the platform supports it, use addAll, otherwise add in loop
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            addAll(data);
        } else {
            for(String item: data) {
                add(item);
            }
        }
    }
}

The @TargetApi(11) annotation is used with ADT 17 to suppress Lint warnings when you have a <uses-sdk android:minSdkVersion="X"/> in the AndroidManifest.xml where X is less than 11. See http://tools.android.com/recent/lintapicheck for more info.

Sean Barbeau
  • 11,496
  • 8
  • 58
  • 111
  • good answer, but let me share with you an experience of mine: if you use "add" method with more than x elements (I've set it to 100 finally), as after every "add" method there will be a "notifydatasetchanged" call, there will be a crash! – Analizer Sep 10 '14 at 12:11
  • @Analizer with that many records, perhaps you're better off using a `CursorAdapter`? See http://stackoverflow.com/questions/21130162/better-to-use-cursor-adapter-or-array-adapter. – Sean Barbeau Sep 10 '14 at 14:28
  • That is not a bad idea, but I'm not using a database, the records are retreived from a JSON response from the server. – Analizer Sep 11 '14 at 08:38
  • 2
    I've added `setNotifyOnChange(false);` / `notifyDataSetChanged();` pair: if you have hundreds of items (~800 in my case), this improves performance dramatically (1500 ms vs <1 ms), as far as this prevents invoking `notifyDataSetChanged()` after every `add()`. – Nikita Bosik Jan 25 '15 at 01:19
11

The simplest way is avoid using ArrayAdapter.addAll() and ArrayAdapter.add() within a loop, like idiottiger suggested in his answer.

If you insist to use ArrayAdapter.addAll(), The short answer is DIY. Check out the source of android.widget.ArrayAdapter here, the actual implementation is much simpler than you thought. there are many alternatives to achieve this, for instance:

  • Option 1: Implement you own ArrayAdapter extends android.widget.BaseAdapter, you get full control of private instance variable and method and can define whatever method your want in your own implementation. there are many tutorial on the internet tells how to create custom adapter, like here and here.
  • Option 2: Implement you own ArrayAdapter extends android.widget.ArrayAdapter, then add the required public method addAll() to your own ArrayAdapter implementation, you don't have the visibility on private member in android.widget.ArrayAdapter so need use existing public API ArrayAdapter.add() to add every single element within a loop.

Option 1 is preferred and used very commonly, especially in the situation when you need render more complex custom UI stuff within your ListView.

yorkw
  • 40,926
  • 10
  • 117
  • 130
  • 1
    wouldn't it be overkill to implement his own version of ArrayAdapter from BaseAdapter just to have an addAll method? Building it from the ArrayAdapter should be enough. – josephus Mar 13 '12 at 03:19
  • Yeah, still worth to check the source to understand how Adapter is implemented in general. – yorkw Mar 13 '12 at 03:24
  • 3
    this is somewhat unrelated, but check out this post if you're using Eclipse to develop Android applications: http://www.helloandroid.com/content/gold-android-developers-add-aosp-source-code-eclipse-android-sources-plugin – josephus Mar 13 '12 at 03:39
  • Thanks, I suppose this is the answer. Unless it's possible to somehow use the `notifyDataSetChanged()` method like Josephus suggested below? How does this method work? – mattboy Mar 13 '12 at 07:22
  • @mattboy, notifyDataSetChanged() is taken care by the underlying framework (defined in android.widget.BaseAdapter), every class extends should have this feature inherited, like the built-in API android.widget.ArrayAdapter. – yorkw Mar 13 '12 at 08:25
4

I combined barbeau and Villarey's answers into what I think is a good solution:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setData(List<String> data) {
    clear();
    if (data != null) {
        addAll(data);
    }
}


@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void addAll(String... items) {
    //If the platform supports it, use addAll, otherwise add in loop
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        super.addAll(items);
    }else{
        for(String item: items){
            super.add(item);
        }
    }
}
datu-puti
  • 1,306
  • 14
  • 33
3

I built on other peoples code online and I created this. Just use this class wherever needed instead of the ArrayAdapter class.

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.widget.ArrayAdapter;

import java.util.Collection;
import java.util.List;

public class ArrayAdapterCompat<T> extends ArrayAdapter<T> {
    public ArrayAdapterCompat(Context context, int resource, List<T> entries) {
        super(context, resource, entries);
    }

    /**
     * Add all elements in the collection to the end of the adapter.
     * @param list to add all elements
     */
    @SuppressLint("NewApi")
    public void addAll(Collection<? extends T> list) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            super.addAll(list);
        } else {
            for (T element : list) {
                super.add(element);
            }
        }
    }

    /**
     * Add all elements in the array to the end of the adapter.
     * @param array to add all elements
     */
    @SuppressLint("NewApi")
    public void addAll(T... array) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            super.addAll(array);
        } else {
            for (T element : array) {
                super.add(element);
            }
        }
    }
}
KVISH
  • 12,923
  • 17
  • 86
  • 162
1

using textList.add method, loop in stringlist, and one by one added to the textList.

idiottiger
  • 5,147
  • 2
  • 25
  • 21
1

You can extend ArrayAdapter on a custom class, and implement your own addAll method. It would be simpler if you use an ArrayList instead of String array, so you can add data without rebuilding the whole data set.

Edit: I remembered that you can just modify the supplied array (or arraylist) that you fed into your adapter, and then call notifyDataSetChanged. This should update your list.

josephus
  • 8,284
  • 1
  • 37
  • 57
  • So you mean in this case I would simply change the values of the variable `stringList` then call `textList.notifyDataSetChanged();`? I have actually tried this but it didn't work. Am I missing something? I think I read somewhere else that this method only works if you edited the adapter `.add` or `.clear` or `.remove`? – mattboy Mar 13 '12 at 07:06
0

I came across this thread late, but the following solution was easy to implement for me:

public class CustomAdapter extends ArrayAdapter<String>...

public void setData(ArrayList<String> items) {
    clear();
    setNotifyOnChange(false);
    if (items != null) {
        for (String item : items)
            add(item);
    }
    notifyDataSetChanged();
}
elprl
  • 1,940
  • 25
  • 36
0

just you need to simple iteration over the collection with if statement for the version

        //mForecastAdapter.clear();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mForecastAdapter.addAll(weekForecast);
        }else {
            for (String s : strings) {
                mForecastAdapter.add(s);
            }
        }
Basheer AL-MOMANI
  • 14,473
  • 9
  • 96
  • 92