Android アプリの Java ソースコードを Kotlin に変換して躓いた点をまとめる(2017年版)

# 概要
2017年8月、Java で開発していた Android アプリのプロジェクトを Kotlin 化した際に躓いた点について紹介します。

## 変換対象の概要
Java で開発していた変換前のプロジェクト、概要は下記の通りでした。

- クラスファイル 140 程度
- Data Binding 導入済み
- Retrolambda 導入済み
- Orma 導入済み
- RxJava 導入済み

## 環境

| 項目 | 値
|:---|:---
| Android Studio | 2.3.2
| Java | 1.8.0_131

----

# Java -> Kotlin 変換
Android Studio 2系なら Kotlin のプラグインを入れれば、 3系ならデフォルトで、"Code" メニューに "Convert Java File to Kotlin File" という項目があるので、それを選択すれば変換が可能です。

![スクリーンショット (3).png](https://qiita-image-store.s3.amazonaws.com/0/102004/4587a3da-2292-b885-720f-306967f14350.png)

![スクリーンショット (4).png](https://qiita-image-store.s3.amazonaws.com/0/102004/26561f5d-6aaf-dbdf-6cf4-f8d4a10f1e86.png)

あるいは、パッケージを選択して Ctrl+Alt+Shift+K を押すと、パッケージに所属する複数のソースコードファイルを一気にコンバートできます。

## build.gradle の修正
Kotlin 関連の依存を追記します。

```diff:build.gradle
buildscript {

+    ext.kotlin_version = '1.1.3-2'

    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
    }
}
```

```diff:app/build.gradle
apply plugin: 'com.android.application'
+ apply plugin: 'kotlin-android'
+ apply plugin: 'kotlin-android-extensions'

dependencies {
+    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
```

## Goodbye Retrolambda
Retrolambda に関する依存は Kotlin のプロジェクトでは利用できないので、 build.gradle から削除しましょう。

```diff:build.gradle
buildscript {
    //..
    dependencies {
-        classpath 'me.tatarka:gradle-retrolambda:3.2.0'
```

```diff:app/build.gradle
-apply plugin: 'me.tatarka.retrolambda'

android {
    //...
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
```

複雑なプロジェクトでなければ、自動変換するだけで Kotlin 製のアプリが誕生します。

----

# 躓いた点
自動変換が上手くいかなかった場合は自分で Kotlin のコードを修正していく必要があります。

1. コンパイルエラーが多発する
2. クラスを自動生成してくれる便利な仕組みが動かない
3. 4系の端末で動かせない
4. Service がクラッシュする
5. Javadoc コメントがおかしくなる

## 1. コンパイルエラーが多発する
通常の Java コードだと Kotlin 変換にそれほど問題はないようです。しかし、 Retrolambda を使っていると結構変換に失敗する印象を受けました。IntelliJ IDEA で Java 8 のコードを変換した時も Lambda 式のところは変換が上手くいっていなかったので、Retrolambda の問題というよりは、Java の Lambda 式と Kotlin 変換の相性に原因があるのかと感じました。

### enum & interface
enum の定数値の振る舞いを interface を使って変えているコードは Java の知識しかないと直しようがなかったので、 Kotlin の高階関数と Lambda を学ぶ良いきっかけになりました。(「[enum の定数値ごとに振る舞いを変えるのを Kotlin でやってみる](http://qiita.com/toastkidjp/items/b7913ba398091c4c55a4)」) Java & Retrolambda で Runnable や Consumer や自分で定義したインターフェイス等を使ってコールバックを渡していたような箇所は、 Kotlin だと高階関数と Lambda で書くことができるので、今後コードを整理していきたいところです。

## 2. クラスを自動生成してくれる便利な仕組みが動かない
具体的には Data Binding と Orma でした。どうやら Annotation Processing の問題だったようで、 kapt を導入して解決しました。

### 手順
1. kapt plugin を導入
2. Data Binding の依存を明示的に追加
3. Orma の annotation processor の依存を annotationProcessor から kapt に切り替え

```groovy:app/build.gradle
apply plugin: 'kotlin-kapt'

dependencies {
    kapt 'com.android.databinding:compiler:2.3.1'
    kapt 'com.github.gfx.android.orma:orma-processor:4.2.5'
```

バージョンは当時のものなので、適宜新しいものを用いてください。

### 参考
- [Android’s Data Binding with Kotlin](https://medium.com/@jlkuma/androids-data-binding-with-kotlin-df94a24ffc0f)
- [OrmaWithKotlin(GitHub)](https://github.com/gfx/OrmaWithKotlin/blob/master/app/build.gradle)

## 3. 4系の端末で動かせない
以下のエラーが出てビルドに失敗しました。

```
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:transformClassesWithDexForDebug'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
```

読んでわかる通り、メソッド数制限をオーバーしています。Java で開発していた時は問題なかったのですが……MultiDex にするとビルドが遅くなるので避けたいところでしたが、かといってライブラリなしで開発するのは無理なので、諦めて MultiDex 化するよりありませんでした。

## 4. Service がクラッシュする
これに関しては多分手動で直している最中に間違ったのだと思います。Intent が null になりうるので、 Intent?(Null 許容型) で宣言しないとクラッシュします。

```diff
-    override fun onBind(intent: Intent): IBinder? {
+    override fun onBind(intent: Intent?): IBinder? {
         return null
```

```diff
-    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
```

## 5. Javadoc コメントがおかしくなる
特に空白行が消えていたり * 2つになっていたりします。残念ですが根性で直しましょう。

----

# 教訓

## 一気に変換しない
Kotlin のスキルがあるなら修正は容易でしょう。ですが、私のように Kotlin がまったくわからない状態で全ファイルの変換を実施するのは、暗闇を灯りなしで歩くようなものです。Kotlin への自動変換ミスを自分で直せるスキルと自信がないうちは、少しずつ変換していって、問題がないことを確かめつつ進めましょう。

## テストを書く
テストがないと、変換前と変換後のコードで生成されるプロダクトに違いがないことを確認する方法が手動デバッグしかなくなります。全部の機能を自分で実行して試すのは面倒なのでちゃんとテストコードを書いておきましょう。Kotlin 変換に限らずリファクタリング等でも同じことが言えると思います。

----
# 終わりに
変換直後のリポジトリを git で保存しておくと、Java -> Kotlin の書き換えをどうやるのかの教材として使えそうですし、コンパイルエラーを修正する過程を残しておくと今後別のプロジェクトで変換する際の参考になると感じました。

それと、文法を学んでからプログラミングを始めるのではなく、動かないコードを直しながら書き方を覚えるという学習スタイルもありなのかなと思いました。 [Kotlin Koans](https://try.kotlinlang.org/#/Kotlin%20Koans/Introduction/Hello,%20world!/Task.kt) がそうだなと気づきました。