かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Xamarin.AndroidでSQLiteを使う

XamarinというかAndroidでSQLiteを使う時は、SQLiteOpenHelperというクラスを継承して使います。

こいつのコンストラクタは、コンテキスト、データベース名、カーソルファクトリ、データベースのバージョンという4つの引数を渡すのが一般的です。カーソルファクトリはnullでも良さそうなのですが、今回はカーソルファクトリまで実装してみました。

順を追って説明します。

Cursorの実装

今回は以下のようなテーブルを前提にしています。

create table People (
    id integer primary key autoincrement,
    name varchar(150) not null
);

このテーブルのデータをタイプセーフに取得するためのカーソルを実装します。カーソルは、SQLiteCursorクラスを継承する形で実装します。コンストラクタは親クラスのを呼び出す形で実装して、テーブルの列に対応するプロパティを定義します。

プロパティ内は、GetColumnIndexメソッドで列名から列のインデックスを取得して、インデックスからGet型名のメソッドを使って値を取得しています。

// カーソルクラス
class PeopleCursor : SQLiteCursor
{
    public PeopleCursor(ISQLiteCursorDriver driver, string editTable, SQLiteQuery query)
        : base(driver, editTable, query)
    {
    }

    // タイプセーフに列のデータを取るためのプロパティを定義しておく
    public long Id { get { return this.GetLong(this.GetColumnIndex("id")); } }
    public string Name { get { return this.GetString(this.GetColumnIndex("name")); } }
}

CursorFactoryの実装

次に、CursorFactoryの実装です。こいつは、SQLiteDatabase.ICursorFactoryインターフェースを実装します。実装時のポイントとしてはJava.Lang.Objectを継承しておく点です。これがないとうまく動きません。

NewCursorメソッドで、先ほど作成したPeopleCursorを作成して返します。

// カーソルファクトリ。Java.Lang.Objectを継承するのがポイント
sealed class PeopleCursorFactory : Java.Lang.Object, SQLiteDatabase.ICursorFactory
{
    public Android.Database.ICursor NewCursor(SQLiteDatabase db, ISQLiteCursorDriver masterQuery, string editTable, SQLiteQuery query)
    {
        // 自前のカーソルを作って返す
        return new PeopleCursor(masterQuery, editTable, query);
    }

}

SQLiteOpenHelper

最後に、SQLiteOpenHelperを継承したクラスを作成します。こいつは、コンストラクタで、コンテキスト、データベース名、カーソルファクトリ(nullでもOK)、データベースのバージョンを渡します。

そしてOnCreateメソッドでデータベース作成時の処理(create tableなどを実行する)をして、OnUpgradeメソッドでデータベースのバージョンが上がった時の処理(create tableやalter tableなどを実行する)を行います。

さくっと実装すると以下のような感じになります。

class DatabaseHelper : SQLiteOpenHelper
{
    private const string DbName = "sample.db";
    private const string Table = "People";
    private const int DbVersion = 1;
    private const string CreateTable = @"
        create table People (
            id integer primary key autoincrement,
            name varchar(150) not null
        );";

    public DatabaseHelper(Context context) : base(context, DbName, new PeopleCursorFactory(), DbVersion)
    {
    }
    
    public override void OnCreate(SQLiteDatabase db)
    {
        // テーブルを作って
        db.ExecSQL(CreateTable);

        // テストデータを入れる
        var values = new ContentValues();
        values.Put("name", "tanaka");
        db.Insert(Table, null, values);
    }

    public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
    }

}

Insert, Update, Delete, QueryメソッドがSQLiteDatabaseクラスに定義されているので、これを使ってDBに問い合わせてなんやかんやします。

使ってみる

ActivityでDBに問い合わせを行い、最初の行のデータをTextViewに表示するコード例です。

[Activity(Label = "App10", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
    private string message;

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.Main);

        var db = new DatabaseHelper(this);
        var cursor = (PeopleCursor) db.ReadableDatabase.Query("People", new[] { "id", "name" }, null, null, null, null, "id");
        cursor.MoveToFirst();
        this.message = string.Format("{0} {1}", cursor.Id, cursor.Name);

        this.FindViewById<TextView>(Resource.Id.Main_Label).Text = this.message;
    }
}

画面にMain_LabelというIDのTextViewがあることを前提としています。先ほど作ったDatabaseHelperクラスのインスタンスを作って、ReadableDatabaseのQueryメソッドでデータを取得しています。テーブル名と取得する列名とソート条件を指定しています。nullのところはgroup byとかhavingとかを指定する箇所です。パラメータ名を見れば使い方がわかると思います。

とってきたカーソルはMoveToFirstを呼び出して最初に移動してデータを取得しています。本番では、trueを返すかどうかチェックしたほうがいいと思います。あと、ループで回してListViewに表示するのが本来の使い方だと思いますが今回はサンプルなので一行目だけデータをとってTextViewに設定してます。

まとめ

ICursorFactoryを実装するときにJava.Lang.Objectを継承するというのに気づくまでにすごい時間かかった…。