ActiveAndroid再入門

この記事は[Android Advent Calendar 2014](http://qiita.com/advent-calendar/2014/android)の20日目の記事です。

先日とある勉強会でActiveAndroidを扱ったときに、思ったより使ったことがない人が多かったので、まとめ記事を書いてみたくなりました。
ActiveAndroidについて調べていると、入門的な内容の情報は多いのですがTipsなども含めたものは少ない印象がありましたので、その辺りをメインに書きました。

## 概要
[ActiveAndroid](https://github.com/pardom/ActiveAndroid)は、ActiveRecordスタイルのORM(Object Relation Mapper)です。
最大の特徴は、SQL文を書かずにSQLiteデータベースを保存したり検索したり出来ることです。
また、いくつか簡単な設定をするだけで、データベースまわりの面倒なセットアップを自動で行ってくれます。具体的には、データベースの作成やアップデート、テーブルの作成などはコードを書く必要がありません。ActiveAndroid内で勝手にやってくれます。

2010年頃に開発が始まっているようなので、結構歴史のあるライブラリなんだと思います。Pardomさんという方が開発したのですが、多くのコントリビューターがこのライブラリに貢献しています。日本の方もいらっしゃって、Androidアプリ開発者ならご存知の方も多いかと思いますが、@rejasupotaroさんが名を連ねていたりします。

## 基本のおさらい
公式のwikiに書いてある内容ですので、紹介程度にサッと書くだけに留めます。
基本的なことは[このdocument](https://github.com/pardom/ActiveAndroid/wiki/Getting-started)を見てください。

### 1. アプリへの組み込み方法

ライブラリをダウンロードするには2つの方法があります。Mavenリポジトリからダウンロードする方法と、sonatype NEXUSからダウンロードする方法です。
それぞれ以下のように記述します。

**Maven**

```xml
<dependency>
  <groupId>com.michaelpardo</groupId>
  <artifactId>activeandroid</artifactId>
  <version>3.1.0-SNAPSHOT</version>
</dependency>
```

**Gradle**

```groovy
repositories {
    mavenCentral()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
```

ただし上記はどちらもSNAPSHOT版なので、リリース版を使いたい場合は自分でビルドしてJARファイルを使うしかありません。
ターミナルなどで、以下のように生成します。
`BUILD SUCCESSFUL`となると"build/libs/activeandroid.jar"が生成されるので、これをプロジェクトのlibsフォルダなどにコピーします。

**JAR生成**

```shell
git clone https://github.com/pardom/ActiveAndroid.git
cd ActiveAndroid
./gradlew build
```

### 2. イニシャライズ

ActiveAndroidを使うためには、AndroidManifest.xmlへの記述とApplicationクラスでの初期化が必要です。
AndroidManifest.xmlには、データベースのファイル名とバージョンを設定できます。
以下はAndroidManifest.xmlの記述例です。

```xml
<manifest>
    <application>
        <meta-data android:name="AA_DB_NAME" android:value="Sample.db" />
        <meta-data android:name="AA_DB_VERSION" android:value="1" />
    </application>
</manifest>
```

ApplicationクラスにはActiveAndroidのものを指定することで、アプリ起動時にイニシャライズ処理が動くようになります。
一番簡単な方法は、AndroidManifest.xmlに記述する方法です。

```
<manifest>
    <application android:name="com.activeandroid.app.Application" ...>
        ...
```

主に以下のような理由でAndroidManifest.xmlに記述出来ない場合もあります。

- 既に独自のApplicationクラスを持っている
- 他のライブラリのApplicationクラスを継承した独自Applicationクラスを持っている

その場合には、以下のようにすることも出来ます。

```java
public class MyApplication extended com.activeandroid.app.Application {
    ...
}
```

or

```java
public class MyApplication extended SomeLibraryApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        ActiveAndroid.initialize(this);
    }
    @Override
    public void onTerminate() {
        super.onTerminate();
        ActiveAndroid.dispose();
    }
}
```

### 3. テーブル作成
テーブルを作成するには、ActiveAndroidのModelのサブクラスを作るだけです。
アノテーションでテーブル名を指定すれば、アプリ起動時に自動生成されます。

```java
@Table(name = "Items")
public class Item extends Model {

    @Column(name = "Name")
    public String name;
    @Column(name = "Age")
    public int age;
    
    public Item() {
        super();
    }
}
```

### 4. Insert / Update / Delete / Query

```java
//Insert
Item item = new Item();
item.name = "bebe"
item.age = 6;
item.save();

//Update
item.name = "nagisa";
item.save();

//Delete
item.delete();
```

Queryの書き方は、2種類用意されています。条件にマッチするレコードを1つだけ返す`executeSingle`メソッドと、複数の結果をListで返す`execute`メソッドがあります。

```java
Item item = new Select().from(Item.class).where("name = ?", "bebe").executeSingle();
```


```java
List<Item> items = new Select().from(Item.class).where("age > ?", 0).execute();
```

## ちょっと応用

### - ContentProvider経由で使う

ActiveAndroidでは、ContentProviderと一緒に使う方法も用意されています。
例えばCursorLoaderで使いたい場合や、SyncAdapterで使いたい場合などが考えられます。
その場合、以下のようなルールに従う必要があります。

**1. IDには`_id`を使う**

```java
@Table(name = "Users", id = BaseColumns._ID)
public class User extends Model {
    ...
}
```

**2. ProviderにはActiveAndroidのContentProviderおよびそのサブクラスを使う**

```xml
<application>
    <provider android:authorities="com.example"
        android:exported="false"
        android:name="com.activeandroid.content.ContentProvider" />
    ...
</application>
```

**使用例**

```java
getActivity().getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() {
    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle cursor) {
        return new CursorLoader(getActivity(),
            ContentProvider.createUri(User.class, null),
            null, null, null, null
        );
    }
    ...
});
```

#### \<Tips\>
ActiveAndroidのContentProviderクラスでは、ProviderのAuthoritiesをアプリのパッケージ名に固定しています。
そのためGradleを使ったビルド(AndroidStudio環境)で、パッケージ名をbuildTypeごとに変えている場合などは、Providerがうまく参照できないケースがあります。
その場合は下記のように、build.gradleファイルからパッケージ名を参照するようにしてあげれば解決出来ます。

```xml:AndroidManifest.xml
<application>
    <provider android:authorities="{$appicationId}"
        android:exported="false"
        android:name="com.activeandroid.content.ContentProvider" />
    ...
</application>
```

### - Transaction処理を使う
たくさんのレコードを操作する場合には、トランザクション処理で行うほうが100倍早いらしいです。（[公式サイトより](https://github.com/pardom/ActiveAndroid/wiki/Saving-to-the-database#bulk-insert)）

```java
ActiveAndroid.beginTransaction();
try {
    for (int i = 0; i < 100; i++) {
        Item item = new Item();
        item.name = "test" + i;
        item.save();
    }
    ActiveAndroid.setTransactionSuccessful();
}
finally {
    ActiveAndroid.endTransaction();
}
```

### - DB Migrationを使う
もしアプリの開発途中でDBのカラムを追加したり削除したい場合は、スキーマ・マイグレーションを使うことが出来ます。
`assets/migrations/<NewVersion>.spl`というファイルを作成して、以下のようにSQLスクリプトを書くことで、DBのアップデート処理で実行してくれます。
以下はItemsテーブルにcolorカラムを追加する例です。

```txt
ALTER TABLE Items ADD COLUMN color INTEGER;
```

#### \<Tips-1\>
比較的最近の更新で、SQLスクリプトの書き方にオプションが用意されました。以下でその経緯が詳しく見れます。
[Added a new SQL parser for migrations - PullRequest#215](https://github.com/pardom/ActiveAndroid/pull/215)

どのように使えるかというと、Manifestファイルへの追記することで、スクリプトを複数行に分けて見やすく記述出来たり、コメントを含めることが出来るようになります。

```xml:AndroidManifest.xml
<meta-data android:name="AA_SQL_PARSER" android:value="delimited" />
```

```txt
-- Create table for migration
CREATE TABLE Item2
(
    Id INTEGER AUTO_INCREMENT PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER NULL,
    color INTEGER NULL /* this column is new */
);

-- Migrate data
INSERT INTO Item2
(
    name,
    age,
    color
)
SELECT  name,
        age,
        0 -- there's no such value in the old table
        FROM Item;

-- Rename Item2 to Item
DROP TABLE Item;
ALTER TABLE Item2 RENAME TO Item;
```

#### \<Tips-2\>
migrationが実行されるタイミングには、少し注意が必要です。
.sqlファイルが実行される条件は、前述した`AA_DB_VERSION`の値が増えたときです。ただしActiveAndroid内ではOldとNewの大小でしか判断しないので、Oldの方のバージョンを指定出来ないなどやや融通がききません。

## 便利な使い方

### - Cursorから読み込む
Cursor型で取り出したレコードから、Modelクラスのインスタンスを生成することも出来ます。
前述したContentProviderと使う場合には、重宝します。

```java
public Item getItemFromCursor(Cursor cursor) {
    Item item = new Item();
    item.loadFromCursor(cursor);
    return item;
}
```

### - ContentProviderのBatchと併用する
ContentProviderを使っていると、複数の処理をまとめて行いたいケースが出てきます。
その場合ContentProviderで用意されているBatch処理を使うことになるのですが、この仕組みではアトミック性の確保を自前で実装しなければいけません。

これをActiveAndroidを用いて実装するには、ActiveAndroiのContentProviderのサブクラスを用意し、その中でTransaction処理を組み込んであげる必要があります。
例として以下のような実装が考えられます。

```java
public class CustomContentProvider extends com.activeandroid.content.ContentProvider {
    @Override
    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {
        ActiveAndroid.beginTransaction();
        try {
            final int numOperations = operations.size();
            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
            for (int i = 0; i < numOperations; i++) {
                results[i] = operations.get(i).apply(this, results, i);
            }
            ActiveAndroid.setTransactionSuccessful();
            return results;
        }
        finally {
            ActiveAndroid.endTransaction();
        }
    }
}
```

## まとめ
SQLiteにアクセスしたり操作したりするコードを、一切書かなくてよいことの恩恵は想像以上です。これのおかげでアプリにDBを組み込む負担が半減した気がします。
ただし見て頂いたように、ContentProviderとの相性は良くないので、利用用途によっては他のライブラリやAndroidのフレームワークをそのまま使った方がいいケースもあるなと思います。

また、作者のpardomさんはActiveAndroidでは十分なパフォーマンスが出せないとして、後継ライブラリを開発している([Ollie - Compile-time active record ORM for Android](https://github.com/pardom/Ollie))ことからも、パフォーマンスを重視するアプリには向いていない可能性もあります。