Kotlin で Android アプリを書いたときの思い出

[Kotlin で Android アプリを 1 本書いてみた感想](http://qiita.com/ryugoo/items/27937ddcf8a9bd194bd0)を見て、そういえば自分も(2015-16)年末年始に kotlin を使ってアプリを作ったことを思い出したので感想を書いてみます。

## 結論（個人の感想です

ActivityやFragment,Service なんかは AndroidAnnotations を利用したほうがｽｯと書けてました。なので、

 - UIとかのコンポーネント: Java(+AndroidAnnotations)
 - モデル的なところやテストコード: Kotlin
 
 と言った使い分けを行いました。
 
## 作ったアプリ

適当に API 叩けたら良かったので、[tiqav](http://tiqav.com/) の API を叩いて、 `RecyclerView` で画像を表示するアプリを作りました。

[numa08/TiquaApp](https://github.com/numa08/TiquaApp)

## 依存関係

```groovy
def retrofitVersion = "2.0.0-beta2"
def realmVersion = "0.87.0"

def powerMockVersion = '1.6.4'
def AAVersion = '3.3.2'
def supportVersion = '23.1.1'
def daggerVersion = '2.0'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile "com.squareup.retrofit:retrofit:$retrofitVersion"
    compile "com.squareup.retrofit:converter-gson:$retrofitVersion"
    compile "com.squareup.retrofit:retrofit-mock:$retrofitVersion"
    compile "com.squareup.retrofit:adapter-rxjava:$retrofitVersion"
    compile "com.squareup.retrofit:adapter-rxjava-mock:$retrofitVersion"
    compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.ext.kotlin_version"
    compile "org.jetbrains.kotlin:kotlin-reflect:$rootProject.ext.kotlin_version"
    compile 'org.jetbrains.anko:anko-sdk15:0.7.2'
    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'org.jetbrains.anko:anko-sdk15:0.7.2'
    compile "io.realm:realm-android-library:${realmVersion}@aar"
    compile "io.realm:realm-annotations:${realmVersion}"
    kapt "io.realm:realm-annotations:${realmVersion}"
    kapt "io.realm:realm-annotations-processor:${realmVersion}"
    testCompile "org.robolectric:robolectric:3.0"
    testCompile 'org.apache.commons:commons-lang3:3.4'
    
    compile "com.android.support:appcompat-v7:${supportVersion}"
    compile "com.android.support:recyclerview-v7:${supportVersion}"
    compile "com.android.support:cardview-v7:${supportVersion}"
    compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.ext.kotlin_version"
    testCompile 'org.robolectric:robolectric:3.0'
    testCompile "org.powermock:powermock-module-junit4:${powerMockVersion}"
    testCompile "org.powermock:powermock-module-junit4-rule:${powerMockVersion}"
    testCompile("org.powermock:powermock-api-mockito:${powerMockVersion}") {
        exclude module: 'mockito-all'
    }
    testCompile 'org.mockito:mockito-core:1.10.19'
    testCompile "org.powermock:powermock-classloading-xstream:${powerMockVersion}"
    apt "org.androidannotations:androidannotations:$AAVersion"
    compile "org.androidannotations:androidannotations-api:$AAVersion"
    compile "com.google.dagger:dagger:$daggerVersion"
    apt "com.google.dagger:dagger-compiler:$daggerVersion"
    provided "javax.annotation:javax.annotation-api:1.2"
    compile 'com.squareup.picasso:picasso:2.5.2'
}
```

実はプロジェクトを

 - API を叩くライブラリ(Kotlin): tiqa4k
 - ライブラリのテスト: tiqa4k-test
 - アプリ本体: app
 
と3つのモジュールに分けています。

ざっくりと依存ライブラリを紹介します。

### 全体的な

[AndroidAnnotations](http://androidannotations.org/) を利用しました。確かに、View をコントローラーから Data Binding でも良いのですが、Android Annotations には、 Activity や Service, Fragment などの生成、呼び出し、パラメータの指定を簡単に行う仕組みがあります。

```java
@EIntentService
public class LoadTiqavService extends AbstractIntentService{

    @ServiceAction
    void loadNewest() {
        // サービスの中でやりたいこと
        // メソッドのパラメータには Parcelable, Serializable あるいはプリミティブな型を指定できる
    }
}

// 呼び出し側
LoadTiqavService_
                .intent(getContext())
                .loadNewest()
                .start();
```

デフォルトだと、 `Intent` インスタンスを作り `Bundle` にパラメータを指定し更に呼び出された側でも `Bundle` からパラメータを取り出したりと言ったコードが発生します。そう言った部分を省略できるメリットは大きいです。

ただし、 AndroidAnnotations は 今のところ kotlin と同居はできません。そのため、Activity,Fragment,Serviceといった AndroidAnnotations を利用したい箇所では Kotlin を使いませんでした。

### ネットワーク通信

[square/retrofit](http://square.github.io/retrofit/) を利用しました。 kotlin から利用をしていると言って特に意識をする部分もありませんでした。

### データベース

昨年、~~苦しめられた~~ お世話になった [realm](https://realm.io/jp/) を利用しました。kotlin で Realm にマッピングされるクラスを書く場合、

 - `open` なクラスにしなければならない
 - コンストラクタのフィールドは `var` にしなければならない
 
といったような注意事項がありました。また、`RealmObject`のサブクラスはメソッドの追加、継承ができません。そのため、`Nullable`な`getter`を生やすとかそう言ったことができず、なんか kotlin っぽくない（？）感じになりました。


```kotlin
public open class Tiqav(@PrimaryKey public open var id : String = "",
                        public open var ext : String = "",
                        public open var height : Int = 0,
                        public open var width : Int = 0,
                        @SerializedName("source_url") public open var sourceURL : String = ""
                  ) : RealmObject() {}
```

そういえば、拡張関数は試してないのですが `RealmObject` のサブクラスに対しても拡張とかってできるんでしょうかね？

### JSONパーサー

Realm を利用している関係上、公式サイトにサンプルも掲載されている [google/gson](https://github.com/google/gson) を利用しました。これも kotlin から利用する上でそんなに困ることも無かったです。

### DI

コンポーネントのテストを行う際、 Realm や Retrofit のサービスを依存性の注入によって参照させてあげるとテスト用の物が利用できて便利です。AndroidAnnotations にも DI のための機能は一応ありますがテストとの相性が良くないので、 [google/dagger2](http://google.github.io/dagger/) を利用しました。

Realm のインスタンスを作るために必要な `RealmConfiguration` や、 Retrofit のサービスを Activity/Fragment/Service などのコンポーネントにDIします。

### テスト

CI環境を構築してテストの実行を想定した場合、エミュレーターによるテストは正直なところ現実的ではありません。そこで、 [robolectric/Robolectric](http://robolectric.org/) でテストを行います。その際、 Realm のモックが必要となるので、 [jayway/powermock](https://github.com/jayway/powermock) を使いました。

テストについては kotlin で記述を行いました。細かいところですがモックを作成するときに利用する `when` メソッドや、アサーションに使う`is`ですが、 kotlin では予約語として存在するため ``when`` や ``is`` と、バッククォートでくくってあげる必要がありました。

また、`@Rule`アノテーションについても `@get:Rule` とする必要があります。

## まとめ

個人的な感想ですが、Androidではやはりコンポーネント間通信にかかるコードが長いイメージがあり、そのためそのあたりの削減には AndroidAnnotations が適していると感じました。

Kotlin を利用することで無名関数などの記法が楽になったり、あるいは Nullable を利用してより安全なコードを書くことができたりとメリットはあります。しかし、AndroidAnnotations の便利さを知っていると、 Kotlin が万能とは言い難い感じでした。

kotlin から AndroidAnnotations が使えたら最強なのかもしれない・・・。

Android のコンポーネントから独立をしている部分とかについては、 kotlin で書くとスマートになると思います。

## 余談

昔、Scala で Android アプリを書いた時にも似たようなことを思ったものだ・・・。