ExpandableListView で SimpleCursorTreeAdapter を使用する際の落とし穴

 見事にハマりました。FMS Player の曲一覧は ExpandableListVIew を用いて表示しています。Group としてアルバム名の一覧が表示されます。アルバムを押すと Child としてアルバム内の楽曲の一覧が表示されるというUIです。当初は、SimpleExpandableListAdapter を用いて実装していました。この Adapter は表示する内容をメモリ上に持ちます。ですので大量のデータを表示するのは難しいと思ってました。試しに私の楽曲データ、1800アルバム、 30000 曲以上を読み込ませてみたところ、見事に Out of memory で落ちてしまいました。そこで登場したのが SimpleCursorTreeAdapter です。ところが、この Adapter が曲者だったんです。

 まずは、SimpleCursorTreeAdapter について簡単に説明します。SimpleCursorTreeAdapter は abstruct クラスで、継承したクラスを作成して、Cursor getChildrenCursor(Cursor groupCursor) を実装する必要があります。FMS Player の場合、アルバム内の楽曲一覧 Cursor を取得することになります。実際のコードは、

    public class MusicListAdapter extends SimpleCursorTreeAdapter {

    	public MusicListAdapter(Context context, Cursor cur,
    			int groupLayout, String[] groupFrom, int[] groupTo,
    			int childLayout, String[] childrenFrom, int[] childrenTo) {
    		super(context, cur, groupLayout, groupFrom, groupTo,   
                    childLayout, childrenFrom, childrenTo);
    	}
    	
		@Override
		protected Cursor getChildrenCursor(Cursor groupCursor) {
			String album = musicDBHelper.getColumnString(groupCursor, Music.ALBUM);
			Cursor cur = musicDBHelper.GetAlbumMusic(album);
			return cur;
		}
    	
    }

のようになります。やっていることは、musicDBHelper.getColumnString(groupCursor, Music.ALBUM) で groupCursor からアルバム名を取得、musicDBHelper.GetAlbumMusic(album) で、そのアルバムに含まれる楽曲の Cursor を返しているだけです。

次に、実際に Adapter を生成して登録するコードは、

    private void ShowMusicList(String genre, String name, String artist) {
        
        try {
                categoryCursor = musicDBHelper.GetAlbums(genre, name, artist);

                MusicListAdapter adapter = new MusicListAdapter(
                                        this,
                                        categoryCursor,
                                        R.layout.music_list_album_item,
                                        new String[] {Music.ALBUM},
                                        new int[] {R.id.groupText1},
                                        R.layout.music_list_track_item,
                                        new String[] {Music.NAME, Music.ARTIST},
                                        new int[] {R.id.childText1, R.id.childText2}
                                        );
                        
                        musicList.setAdapter(adapter);
        }
        catch(Exception e) {
                Log.e(TAG, e.toString());
        }
    }

のようになります。MusicListAdapter を new する際に、第2引数に musicDBHelper.GetAlbums(genre, name, artist) で取得したアルバム一覧の Cursor を指定しています。このカーソルを用いて Group 一覧が表示されるのでしょう。そして Group をクリックした時に先のコードで示した getChildrenCursor() が呼ばれて Child 一覧が表示されるという仕組みだと思われます。

 これで間違いはないはずなのですが、動かしてみるとエラーが発生しました。

java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.

どうやら Cursor からデータを取得できていないようだということはわかります。musicDBHelper.GetAlbums()  は内部でアルバム名のカラムを指定してデータを取得しており、musicDBHelper.GetAlbumMusic() は内部で、TrackIDというキー項目と、楽曲名とアーティスト名のカラムを指定していました。どれも取得できないはずはないのです。実際、それぞれのメソッドを単体で動かすと問題なく動きました。

 ちなみに、SQLite に作成した Music テーブルの構造は以下のとおりです。

create table Music (
        Track_id        integer primary key,
        No              integer,
        Name            text,
        Artist          text,
        Composer        text,
        Album           text,
        Genre           text,
        Disc_number     integer,
        Disc_count      integer,
        Track_number    integer,
        Track_count     integer,
        Date_modified   text,
        Date_added      text,
        Location        text
)

当初 まったく見当がつかなかったのですが、別のところから答えが見つかりました。ExpandableListView の Click Listner です。

        private ExpandableListView.OnChildClickListener mMusicListChildClickListenner = new ExpandableListView.OnChildClickListener() {

                @Override
                public boolean onChildClick(ExpandableListView parent, View view,
                    int groupPosition, int childPosition, long id) {

                    return false;
                }
        }

onChildClick() の最終引数に long id というのがあります。この id は当初システムで生成されるものだと思ったのですが、、もしかしたらデータベースから ID を返す必要があるのではないか?とすると、そのIDカラムをどのように指定するのか?Music テーブルの場合は Track_id がプライマリキーです。

Googleを検索してサンプルを色々と見てみると、プライマリキーのカラムが _id となっていることに気づきました。そこで Music テーブルの Track_id カラム名を _id に変更するとビンゴ!!見事に動きました。

テーブルのプライマリキーは _id とするというのが Android の流儀なんでしょうかね。見事にハマりました。

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