[Android]自前のArrayAdapterを定義したらデータ更新されなかった

自前のAdapterを作って、ListViewを使ったものを作っていた時にハマった経験があり、解決方法を残しておきます。

データをaddしても画面に反映されなかったり、落ちたりと。
ビルドエラーやワーニングはでてなかったし、デバッグしてもデータが変に消えているわけではない。。。

結論

  • ArrayAdapterをカスタマイズする場合、Adapterクラス内で自前でデータを管理してはならない。
  • カスタマイズしたArrayAdapterに渡すデータは必ずsuperのコンストラクタに渡す
  • ArrayAdapterに対する操作はコンストラクタで渡したデータに対して行われる

詳細

  • ダメなカスタムArrayAdapterの例

    public class NewArrayAdapter extends ArrayAdapter<String> {
    // データをメンバで管理する。★ここが原因★
    ArrayList<String> mData;
    
    public NewArrayAdapter(Context context, int resource, ArrayList<String> data) {
        super(context, resource);
         // こうやるとNG
        mData = data;
    }
    }

    アダプタにセットされるデータをメンバ変数として定義して自前で管理することは絶対にやってはいけない。
    この実装では、アダプタに対してデータ操作を実行しても操作内容がリストに反映されない。

  • 呼び出し例

    ArrayList<String> data = new ArrayList<String>();
    int resId = getResource().getLayout(R.layout.list_item);
    
    NewArrayAdapter<String> adapter = new NewArrayAdapter<String>(this, resId, data);

ArrayAdapterのコンストラクタについて

ダメな例のArrayAdapterのコンストラクタ内では、super(context, resource)(引数二つ)が呼ばれいてる。
※ArrayAdapterのコンストラクタは5種類あります。

  • 引数二つのコンストラクタで行っていること
    コンストラクタは内部でinit()を呼んでいるだけ
    ※他のコンストラクタも全て同じ感じ

    /**
     * Constructor
     *
     * @param context The current context.
     * @param resource The resource ID for a layout file containing a TextView to use when
     *                 instantiating views.
     */
    public ArrayAdapter(Context context, int resource) {
        init(context, resource, 0, new ArrayList<T>());
    }

    じゃあ、init()って何やっている?

  • init()の処理

    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;
    }

    mObjectsはアダプタにセットされるデータの実体。
    mObjects に 受け取ったobjectsを代入している

アダプタに対する操作は、基本的にこのmObjectsに対して行われる。
例えば、addした場合の処理を確認する。

  • add()の処理
    /**
     * Adds the specified object at the end of the array.
     *
     * @param object The object to add at the end of the array.
     */
    public void add(T object) {
        synchronized (mLock) {
            if (mOriginalValues != null) {
                mOriginalValues.add(object);
            } else {
                mObjects.add(object);
            }
        }
        if (mNotifyOnChange) notifyDataSetChanged();
    }

    mOriginalValuesはデータをフィルタリングした際に使用されるメンバ。(今回は関係なし)
    アダプタに対するadd呼び出しはmObjectsのaddを呼び出している。
    ※insertやclearなど他のメソッドでも同様

mObjectsに適切なデータが更新されていなければ、データも更新されない。

ArrayAdapter(Context context, int resource) では、initの第4引数に空のリストを渡している。
つまり、アダプタのデータには空のリストが設定される。

定義したArrayAdapterのコンストラクタに合わせて、呼ぶsuper()(親のコンストラクタ)も変えてあげた方がよいです。

コメント

タイトルとURLをコピーしました