Androidはワンツーパンチ 三歩進んで二歩下がる

プログラミングやどうでもいい話

ContentProvierとCursorLoaderとFragmentを使ったサンプルを作ってみた

最近のAndroid
・Activity→Fragmentへ
・DB処理は非同期に→非同期でクエリを発行するのに最適なCursorLoaderがナウい
・SQLiteOpenHelperのサブクラスで直にDBを触る→ContentProviderを通してアクセスする

ということらしいです。
そこでかなり乗り遅れてしまいましたが、この波に乗ってみようと上記を盛り込んだ
サンプルプロジェクトを作成いたしました。
上でわざわざContentProviderを挙げたのは、CursorLoaderはContentProvider経由
で使用することになっているからです。このサンプルはCursorLoaderの使い方を勉強したくて作りました。
http://developer.android.com/intl/ja/guide/components/loaders.html
ContentProviderを使用することでSQLインジェクション対策にもなるようですよ。
今後もどんどん使っていこうと思います。

サンプルプロジェクトのダウンロードはこちら
GitHubこちら
私のスキルはとても低いので、何かおかしいことやもっと良いやり方があったら教えてくれたら
すごく嬉しいです。

サンプルの内容は、
・DBの内容をListViewに表示する
・ListViewのアイテムがクリックされたらそのアイテムの詳細画面を表示する
というものです。よくあるListViewの使い方だと思います。
CursorLoaderを使って、全件DBを読み込み、1件のみDBを読み込みという処理を行なっております。

画面の遷移はFragmentを使っていますが、
Fragmentにデータを受け渡すやり方(Bundleを使う)などサンプルでも出てきております。
私同様初心者の方のお役に立てれば幸いです。

サンプルの画面のイメージはこんな感じ。

起動するとリストが表示されます。

リストのアイテムをクリックすると詳細画面が表示されます。

サンプルコードは7本に分かれていますので、1本だけブログにも貼ります。
CursorLoaderを使用しています。

package com.example.myexamplecursorloader;

import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;

import com.example.myexamplecursorloader.data.Place;
import com.example.myexamplecursorloader.data.PlaceManager;

import java.util.ArrayList;
import java.util.List;

/**
 * CursorLoaderを使ってテーブルのデータをListViewに表示します
 * 
 * @author sakura
 */
public class PlaceListFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> { // ←これを実装する必要がある

    private SimpleCursorAdapter mAdapter;
    List<Place> mPlaces;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.list, container, false);

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Loaderの初期化
        getLoaderManager().initLoader(0, null, this);

        // ListViewにセットしたいテーブルのフィールドを配列にセット
        String[] from = new String[] {
                PlaceManager.Place.KEY_PLACE,
                PlaceManager.Place.KEY_URL
        };
        // ListViewの各ビューを配列にセット
        int[] to = new int[] {
                R.id.place,
                R.id.url
        };
        // アダプターに一行のレイアウトをセット
        mAdapter = new SimpleCursorAdapter(getActivity(), R.layout.list_item, null,
                from, to,
                SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

        // アダプターをListViewにセット
        ListView listView = (ListView) getView().findViewById(R.id.listview);
        listView.setAdapter(mAdapter);
        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position,
                    long id) {
                // 詳細画面の呼び出し
                replaceFragment(position);
            }
        });
    }

    private void replaceFragment(int position) {
        PlaceDetailFragment fragment = new PlaceDetailFragment();
        Bundle bundle = new Bundle();
        Place p = mPlaces.get(position);
        bundle.putString("place_id", p.getPlaceID());
        fragment.setArguments(bundle);

        FragmentTransaction ft = getActivity().getSupportFragmentManager()
                .beginTransaction();
        ft.replace(R.id.LinearLayout01, fragment, "PlaceDetailFragment");
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        ft.addToBackStack(null);
        ft.commit();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        // Loaderの廃棄
        getLoaderManager().destroyLoader(0);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
        // CursorLoader生成(検索条件の指定)
        return new CursorLoader(this.getActivity(),
                PlaceManager.Place.CONTENT_URI, null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        Cursor old = mAdapter.swapCursor(cursor);
        if (old != null) {
            old.close();
        }
        if (cursor == null) {
            mPlaces = null;
            return;
        }
        mPlaces = new ArrayList<Place>();
        if (cursor.moveToFirst()) {
            do {
                Place place = new Place();
                place.setID(cursor.getInt(0));
                place.setPlaceID(cursor.getString(1));
                place.setPlace(cursor.getString(2));
                place.setUrl(cursor.getString(3));
                // Adding to list
                mPlaces.add(place);
            } while (cursor.moveToNext());
        }

    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        Cursor old = mAdapter.swapCursor(null);
        if (old != null) {
            old.close();
        }
    }

}

2013/02/09追記
@kimukou_26 さんとブログ記事のやりとりしたときに上記のコードをActivity(FragmentActivity)からCursorLoaderを使うように書き換えたものを作りました。
検証はろくにしてないのですがソースはこちらです。

2013/04/21追記
アダプターにCursorAdapterを拡張したものを使用するサンプルを作りました。
よろしければご覧くださいませ。
CursorLoaderを使ってデータを読み込み、CursorAdapterをカスタマイズしたアダプターを使いListViewに表示する