[Android]ArrayAdapterをカスタマイズする時に絶対にやってはいけないこと

先日、ArrayAdapterに対してadd()やclearなどの操作をしてもなぜか画面に反映されないという原因不明のバグを調査していたが、原因が判明したのでメモしておく。

このバグはプログラミング初心者よりもむしろある程度経験を積んだ人ほどやってしまいそうなバグで、一見問題なさそうに見えるのでかなり厄介だと思う。ArrayAdapterをカスタマイズする時は絶対にこのバグを作りこまないように注意しましょう。

スポンサーリンク

データを自前で管理しない!

結論から言うと、ArrayAdapterをカスタマイズする場合、絶対に自前でデータを管理してはならない。どういうことかというと、ArrayAdapterをカスタマイズして独自のアダプタを作る際、アダプタのデータをメンバで管理してはならないということである。

ダメなサンプルとして以下のSampleArrayAdapterクラスを示す。

データを自前で管理するメリットは、データを操作したりカスタマイズしたアクセッサを簡単に実装できるなど色々考えられるが、アダプタにセットされるデータを自前で管理することは絶対にやってはいけない

このようなカスタムアダプタを作ってしまうと、アダプタに対してデータ操作を実行しても操作内容がリストに反映されないというバグが発生する。

ArrayAdapterのコンストラクタを解剖

ここで注目すべきなのはSampleArrayAdapterクラスのコンストラクタの中身である。SampleArrayAdapterのコンストラクタ内ではsuper=ArrayAdapterの引数が2つのコンストラクタが呼ばれている。

ArrayAdapterのドキュメントを見ると、コンストラクタが5種類定義されている。ArrayAdapterをインスタンス化する時、通常よく使われるのはコンテキスト、レイアウトID、データ(配列かList)の3つの引数をとるコンストラクタである。

例えばこんな感じ。

これでdataの中身がアダプタにセットされて、画面表示とデータが適切に連携されるようになる。

では引数が2つのコンストラクタが呼ばれた場合、データには何がセットされるのだろうか。ということでソースを確認してみる。ArrayAdapterのコンストラクタのソースは以下のようになっている。

コンストラクタは内部でinit()というメソッドを呼んでいるだけのようである。他のコンストラクタも全て同じようになっている。ではこのinitが何者なのか、ソースを見てみる。

initではコンストラクタに渡されたデータをメンバにセットしている。重要なのは5行目のmObjects = objects;である。mObjectsはアダプタにセットされるデータの実体である。

アダプタに対する操作は、基本的にこのmObjectsに対して行われる。例えばArrayAdapter.add(T object)は以下のようになっている。

mOriginalValuesはデータをフィルタリングした際に使用されるメンバなので、今回は関係ない。重要なのは、アダプタに対するadd呼び出しはmObjectsのaddを呼び出しているということである。

これはinsertやclearなど他のメソッドでも同様である。つまり、mObjectsに適切なデータがセットされていなければアダプタに対する操作は全て無意味となる。

適切なコンストラクタを使用する

ここまできたらようやくSampleArrayAdapterでなぜバグが発生するのかがわかってくる。SampleArrayAdapterのコンストラクタでは引数が2つのsuperのコンストラクタが使われていた。

改めてsuper(=ArrayAdapter)の引数が2つのコンストラクタを見てみよう。

見ての通り、initの第4引数に空のリストを渡している。つまり、アダプタのデータには空のリストが設定される

SampleArrayAdapterのコンストラクタは引数が3つなので、このクラスを使うコードは以下のようになる。

通常のArrayAdapterの呼び出しと何も変わらない。もちろんコンパイルエラーも起きないし、アダプタに対してaddやclearを行ってもエラーは発生しない(removeを行うとエラーになる可能性は高い)。

しかし実際にはコンストラクタに渡したデータはアダプタの内部構造と紐付いていないので、いくらアダプタに対してデータ操作をしても、それが画面に反映されることはない。また、SampleArrayAdapterのmDataの値を直接操作したところで、アダプタのデータとしてセットされていないのでこれも画面に反映されることはない。

このように、呼び出し側としては通常通りにアダプタにデータをセットしているつもりだが、実際にはアダプタとデータの連携がとれておらず、しかも適切に操作を行えているように見えるという、非常に原因がわかりにくいバグが発生することになった。

まとめ

以上、長くなったが重要な点をまとめておく。

  • ArrayAdapterをカスタマイズする際はデータをメンバにしない
  • カスタマイズしたArrayAdapterに渡すデータは必ずsuperのコンストラクタに渡す
  • ArrayAdapterに対する操作はコンストラクタで渡したデータに対して行われる

コメント

  1. […] [Android]ArrayAdapterをカスタマイズする時に絶対にやってはいけないこと | コール イット ティップス […]

  2. […] [Android]ArrayAdapterをカスタマイズする時に絶対にやってはいけないこと […]