ListView使うときにセットでArrayAdapterも使用すると思いますが、ArrayAdapterの使い方で予想外にハマってしまいました(^_^;)
調べて見るとJavaプログラマーにとっては良くハマるポイントらしいので覚えておこうと思います。
やりたかった事
ユーザの操作結果をもとにListViewにアイテムを追加する。
失敗コード
例えばこんな感じで実装したとします。
public class MainActivity extends Activity { private ArrayAdapter<String> mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String[] items = {"ダウンタウン", "バナナマン", "オードリー"}; ListView listView = (ListView)findViewById(R.id.listView1); mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items); listView.setAdapter(mAdapter); Button btnIns = (Button)findViewById(R.id.button1); btnIns.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mAdapter.insert("東京03", 0); } }); } }
この場合、ボタンをクリックするとjava.lang.UnsupportedOperationExceptionでアプリが落ちます。
ちなみに、insertじゃなくてadd,remove,clearでも落ちます。
なぜか
結論を言うとArrayAdapterインスタンスを作るときに使用するコンストラクタによって、後から追加、削除ができるかが決まります。
ArrayAdapter.javaを見てみると・・・
//省略 public ArrayAdapter(Context context, int resource) { init(context, resource, 0, new ArrayList<T>()); } //省略 public ArrayAdapter(Context context, int resource, T[] objects) { init(context, resource, 0, Arrays.asList(objects)); } //省略 public ArrayAdapter(Context context, int resource, List<T> objects) { init(context, resource, 0, objects); } //省略 private void init(Context context, int resource, int textViewResourceId, List<T> objects) { mContext = context; mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mResource = mDropDownResource = resource; mObjects = objects; mFieldId = textViewResourceId; } //省略 public void insert(T object, int index) { synchronized (mLock) { if (mOriginalValues != null) { mOriginalValues.add(index, object); } else { mObjects.add(index, object); } } if (mNotifyOnChange) notifyDataSetChanged(); }
initメソッドの第4引数がArrayAdapterで保持するListオブジェクト(mObjects)になり、insertやaddメソッドではmObjectsのadd()を呼び出しています。
コンストラクタを見るとinitの第4引数に渡すオブジェクトが異なることがわかります。
2行目のコンストラクタ :ArrayList
6行目のコンストラクタ :Arrays.asList(..) ← 今回問題のExceptionがでるコンストラクタ
14行目のコンストラクタ:コンストラクタに渡したオブジェクト
6行目のコンストラクタではmObjectsをArrays.asList()で生成しているのですが、Arrays.asList()で生成されるオブジェクトはArraysクラスのインナークラスArrayList(注:java.util.ArrayListではない)で、このArrayListクラスにはadd,removeなどが実装されていないのです。
よって、未実装のメソッドを呼び出すことになり、Exceptionが投げられてたという訳・・・というか罠。
Arrays.asList()のマニュアルには
「Returns a List of the objects in the specified array. The size of the List cannot be modified, i.e. adding and removing are unsupported, but the elements can be set. ・・・」
あ・・書いてありますね。ArrayAdapterの方にも書いてあれば・・・。
修正後コード
という訳で、下記のように修正してあげれば期待する振る舞いとなります。
// String[] items = {"ダウンタウン", "バナナマン", "オードリー"}; ArrayList<String> items = new ArrayList<String>(Arrays.asList("ダウンタウン", "バナナマン", "オードリー")); ListView listView = (ListView)findViewById(R.id.listView1); mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items); listView.setAdapter(mAdapter);
今回の件は、実は問題発生から解決までにそんなに時間がかかっていません。
というのも、ググッたらすぐに出てくるほどJavaプログラマーにとっては良くある話みたいですね。
とは言え、にわかJavaプログラマーの私のとってはAndroidSDKのコード追ってみたりいい勉強になりました。
参考
Why can’t one add/remove items from an ArrayAdapter?
Unable to modify ArrayAdapter in ListView: UnsupportedOperationException
同じところでハマりましたが、おかげさまで解決しました(^^)
ありがとうございました!