Kotlin で Android アプリを 1 本書いてみた感想を見て、そういえば自分も(2015-16)年末年始に kotlin を使ってアプリを作ったことを思い出したので感想を書いてみます。
結論(個人の感想です
ActivityやFragment,Service なんかは AndroidAnnotations を利用したほうがスッと書けてました。なので、
- UIとかのコンポーネント: Java(+AndroidAnnotations)
- モデル的なところやテストコード: Kotlin
と言った使い分けを行いました。
作ったアプリ
適当に API 叩けたら良かったので、tiqav の API を叩いて、 RecyclerView
で画像を表示するアプリを作りました。
依存関係
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 を利用しました。確かに、View をコントローラーから Data Binding でも良いのですが、Android Annotations には、 Activity や Service, Fragment などの生成、呼び出し、パラメータの指定を簡単に行う仕組みがあります。
@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 を利用しました。 kotlin から利用をしていると言って特に意識をする部分もありませんでした。
データベース
昨年、苦しめられた お世話になった realm を利用しました。kotlin で Realm にマッピングされるクラスを書く場合、
-
open
なクラスにしなければならない - コンストラクタのフィールドは
var
にしなければならない
といったような注意事項がありました。また、RealmObject
のサブクラスはメソッドの追加、継承ができません。そのため、Nullable
なgetter
を生やすとかそう言ったことができず、なんか 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 を利用しました。これも kotlin から利用する上でそんなに困ることも無かったです。
DI
コンポーネントのテストを行う際、 Realm や Retrofit のサービスを依存性の注入によって参照させてあげるとテスト用の物が利用できて便利です。AndroidAnnotations にも DI のための機能は一応ありますがテストとの相性が良くないので、 google/dagger2 を利用しました。
Realm のインスタンスを作るために必要な RealmConfiguration
や、 Retrofit のサービスを Activity/Fragment/Service などのコンポーネントにDIします。
テスト
CI環境を構築してテストの実行を想定した場合、エミュレーターによるテストは正直なところ現実的ではありません。そこで、 robolectric/Robolectric でテストを行います。その際、 Realm のモックが必要となるので、 jayway/powermock を使いました。
テストについては kotlin で記述を行いました。細かいところですがモックを作成するときに利用する when
メソッドや、アサーションに使うis
ですが、 kotlin では予約語として存在するため when
や is
と、バッククォートでくくってあげる必要がありました。
また、@Rule
アノテーションについても @get:Rule
とする必要があります。
まとめ
個人的な感想ですが、Androidではやはりコンポーネント間通信にかかるコードが長いイメージがあり、そのためそのあたりの削減には AndroidAnnotations が適していると感じました。
Kotlin を利用することで無名関数などの記法が楽になったり、あるいは Nullable を利用してより安全なコードを書くことができたりとメリットはあります。しかし、AndroidAnnotations の便利さを知っていると、 Kotlin が万能とは言い難い感じでした。
kotlin から AndroidAnnotations が使えたら最強なのかもしれない・・・。
Android のコンポーネントから独立をしている部分とかについては、 kotlin で書くとスマートになると思います。
余談
昔、Scala で Android アプリを書いた時にも似たようなことを思ったものだ・・・。