かずきのBlog@hatena

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

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