かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Xamarin.AndroidでContentProviderを実装する

SQLiteの使い方がわかったら次はContentProviderですよね。 ということで、SQLの部分はさくっと実装。

using Android.Content;
using Android.Database.Sqlite;

namespace ContentProviderSample
{
    public class PeopleDatabase : SQLiteOpenHelper
    {
        private const string DbName = "people.db";
        private const string TableName = "People";
        private const string CreateTable = @"
            create table People (
                _id integer primary key autoincrement,
                name varchar(150)
            );";
        private const int DatabaseVersion = 2;

        public PeopleDatabase(Context context) : base(context, DbName, new PeopleCursorFactory(), DatabaseVersion)
        {
        }
        
        public override void OnCreate(SQLiteDatabase db)
        {
            db.ExecSQL(CreateTable);
            for (int i = 0; i < 100; i++)
            {
                var c = new ContentValues();
                c.Put("name", "tanaka" + i);
                db.Insert(TableName, null, c);
            }
        }

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

        public PeopleCursor GetAll()
        {
            return (PeopleCursor) this.ReadableDatabase.Query(TableName, new[] { "_id", "name" }, null, null, null, null, "_id");
        }

        public PeopleCursor GetOne(long id)
        {
            return (PeopleCursor) this.ReadableDatabase.Query(TableName, new[] { "_id", "name" }, "_id = ?", new[] { id.ToString() }, null, null, null);
        }
    }

    public 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")); }
        }
    }

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

}

ContentProvider

コンテンツプロバイダーを作っておくと、いろいろ便利らしいので、作っておきます。

using Android.Content;
using System;

namespace ContentProviderSample
{
    // Authorityを属性で設定する
    [ContentProvider(new[] { Authority })]
    public class PeopleContentProvider : ContentProvider
    {
        // URI組立に必要な人たち
        private const string Authority = "com.example.PeopleProvider";
        private const string BasePath = "people";
        public static readonly Android.Net.Uri ContentUri = Android.Net.Uri.Parse("content://" + Authority + "/" + BasePath);

        // Mime
        private const string MimeType = "/vnd.com.example.People";

        // UriMatcherのマッチ結果
        private const int GetAll = 0;
        private const int GetOne = 1;

        private static readonly UriMatcher UriMatcher = CreateUriMatcher();

        // UriMatcherを生成する
        private static UriMatcher CreateUriMatcher()
        {
            var r = new UriMatcher(UriMatcher.NoMatch);
            r.AddURI(Authority, BasePath, GetAll);
            r.AddURI(Authority, BasePath + "/#", GetOne);
            return r;
        }

        private PeopleDatabase db;

        // 列名の定数
        public static class PeopleColumns
        {
            public const string Id = "_id";
            public const string Name = "name";
        }

        // 削除。とりあえず未実装
        public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
        {
            throw new NotImplementedException();
        }

        // Uriに応じてMimeを返す。今回は同じもの返す
        public override string GetType(Android.Net.Uri uri)
        {
            switch (UriMatcher.Match(uri))
            {
                case GetAll:
                case GetOne:
                    return MimeType;
                default:
                    throw new Java.Lang.IllegalArgumentException();
            }
        }

        // 挿入。とりあえず未実装
        public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
        {
            throw new NotImplementedException();
        }

        // 作成時の処理。とりあえずDB作る
        public override bool OnCreate()
        {
            this.db = new PeopleDatabase(this.Context);
            return true;
        }

        // Uriを判別して全件返すか一件返すか処理をわける
        public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
        {
            switch (UriMatcher.Match(uri))
            {
                case GetAll:
                    return this.db.GetAll();
                case GetOne:
                    return this.db.GetOne(long.Parse(uri.LastPathSegment));
                default:
                    throw new Java.Lang.IllegalArgumentException();
            }
        }

        // 更新。とりあえず未実装
        public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
        {
            throw new NotImplementedException();
        }
    }
}

なんか定型でめんどくさい感じがしますね。

使う

ListViewにとりあえず表示してみようと思います。

using Android.App;
using Android.Content;
using Android.Database;
using Android.OS;
using Android.Widget;

namespace ContentProviderSample
{
    [Activity(Label = "ContentProviderSample", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            this.SetContentView(Resource.Layout.Main);

            // ListViewを取得して
            var listView = this.FindViewById<ListView>(Resource.Id.Main_ListViewPeople);

            // 取得する列名を指定して
            string[] projection = new[] { PeopleContentProvider.PeopleColumns.Id, PeopleContentProvider.PeopleColumns.Name };
            // 表示列を指定
            string[] fromColumns = new[] { PeopleContentProvider.PeopleColumns.Name };
            // 表示先コントロールのIDを指定
            int[] toControlIds = new[] { Android.Resource.Id.Text1 };

            // コンテンツプロバイダーからデータを読み込む
            var loader = new CursorLoader(this, PeopleContentProvider.ContentUri, projection, null, null, null);
            var cursor = loader.LoadInBackground() as ICursor;

            // カーソルを使うアダプタを作って、ListViewに設定
            var adapter = new SimpleCursorAdapter(this, Android.Resource.Layout.SimpleListItem1, cursor, fromColumns, toControlIds);
            listView.Adapter = adapter;
        }
    }
}

見事表示できた。

f:id:okazuki:20140727233014p:plain