delete from hateblo.jp where 1=1;

タイトルに意味はありません。

Android-OrmLiteContentProviderを使ってみる

対象

  • ORMLiteを使っている
  • ContentProviderの使用を検討している
  • ormlite-content-provider-compilerを使用したがContractがどこに生成されるのかわからない
  • Android アプリを作っている
背景

Activityもしくは、Fragmentに対してORMLiteに対する依存関係を挿入しないといけない。
→結構手間である

public abstract class OrmLiteFragment<H extends OrmLiteSqliteOpenHelper> extends Fragment {
	//OrmLiteActivityから関連する処理をコピーしてくる
}
仮説

ContentProviderによりデータを供給できるようにすれば、FragmentやActivityからORMLite関連の処理を取っ払うことができるのではないか。

準備

いろいろと準備が必要で、AndroidStudioに関する情報がほとんどない。
android-aptを実装しないといけないらしい。

build.gradle (Project)
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.0.1'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}
allprojects {
    repositories {
        mavenCentral()
    }
}
build.gradle (Module)
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
    compileSdkVersion 21
    buildToolsVersion '21.1.2'
    testBuildType "debug"
    applicationId "org.test"
    defaultConfig {
        //snip
        buildConfigField 'String', 'PROVIDER_ID', 'APPLICATION_ID + ".provider"'
    }
    buildTypes {
        //snip
    }
}

dependencies {
    //snip
    compile 'com.j256.ormlite:ormlite-core:4.+'
    compile 'com.j256.ormlite:ormlite-android:4.+'
    compile 'com.tojc.ormlite.android:ormlite-content-provider-library:1.0.+'
    apt 'com.tojc.ormlite.android:ormlite-content-provider-compiler:1.0.+'
}
org.test.db.entity.Connection.java
package org.test.db.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import com.tojc.ormlite.android.annotation.AdditionalAnnotation;

import org.test.BuildConfig;

@AdditionalAnnotation.Contract()
@DatabaseTable
@AdditionalAnnotation.DefaultContentUri(authority = BuildConfig.APPLICATION_ID, path = "connection")
@AdditionalAnnotation.DefaultContentMimeTypeVnd(name = BuildConfig.PROVIDER_ID, type = "connection")
public class Connection {
	public final static String ID = "id";
	public final static String CONNECTION_ID = "connection_id";
    @DatabaseField(generatedId = true)
	@AdditionalAnnotation.DefaultSortOrder
    private Integer id;
    @DatabaseField
    private String name;
}

結果

Connectionというエンティティを作成すると、自動的に、ConnectionContractという以下のコードが作成される。
公式のReadme.mdではContract.Connection.〜のような記述だったが、ConnectionContractが正しいようである。

package org.test.db.entity;

import android.net.Uri;
import android.content.ContentResolver;
import android.provider.BaseColumns;

public final class RedmineConnectionContract
    implements BaseColumns {
  public static final String TABLE_NAME = "Connection";

  public static final String CONTENT_URI_PATH = "connection";
  public static final String AUTHORITY = "org.test";

  public static final String MIMETYPE_TYPE = "connection";
  public static final String MIMETYPE_NAME = "org.test.provider";

  public static final int CONTENT_URI_PATTERN_MANY = 1;
  public static final int CONTENT_URI_PATTERN_ONE = 2;

  public static final Uri CONTENT_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).appendPath(CONTENT_URI_PATH).build();

  private RedmineConnectionContract() {
  }

  public static final String ID = "id";
  public static final String NAME = "name";
  //snip
}

課題

WHERE句を記述することが困難→解決

サンプルを見ると、簡単にコンテンツプロバイダを実装することができるということまではわかったが、それ以上、情報がなかった。
ソースを見ると、DIRECTORYやITEMのみで、カスタムURLによるデータ返しは、うまいことやらないといけないらしい。
そのため、onQueryをOverrideして、target.getPatternCode()でカスタマイズする必要がありそう。
未検証のため、公開できる情報がないが、そのうち試して公開する。

CursorLoader より、以下のメソッドがある。

public CursorLoader(Context context, Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder)

ここのselectionとselectionArgsに条件を記載することでWhere句の記述が可能となる。
そのため、ContentProvider側で条件を記載する等の操作は不要。
サンプルを記載すると、以下のような形になる。(前述のサンプルとクラス等が異なります)

public static CursorLoader getCursorLoader(Context context, int connection_id){
	return new CursorLoader(context,
			RedmineProjectContract.CONTENT_URI, null
			, RedmineProjectContract.CONNECTION_ID + "=?"
			, new String[]{
				String.valueOf(connection_id)
			}, null
	);
}

public static Cursor getSearchQuery(ContentResolver resolver,int connection_id, CharSequence constraint){
	if(TextUtils.isEmpty(constraint)){
		return resolver.query(RedmineProjectContract.CONTENT_URI
				, null
				, RedmineProjectContract.CONNECTION_ID + "=?"
				, new String[]{
						String.valueOf(connection_id)
				}, null
		);
	} else {
		return resolver.query(RedmineProjectContract.CONTENT_URI
				, null
				, RedmineProjectContract.CONNECTION_ID + "=? AND " + RedmineProjectContract.NAME + " like ?"
				, new String[]{
						String.valueOf(connection_id)
						, "%" + constraint + "%"
				}, null
		);
	}
}
外部キー結合等の場合におけるWhere句の作成方法(調査中)

外部キーの設定を行っているが、そのときの挙動がいまいち不明。
Contractに本来はproject_idとなるべきなのに、projectとなっているため、project._idのようにしないといけないのか不明。


余談

見ていると、どうも日本人が作成しているようだが、英語の情報しかない。
しっかりとしたつくりだが、2014年5月以降、更新が見当たらない。
5月だから、Android Studioが主流になる前のような気がする。