Java製AndroidアプリをKotlinに変換する時にやると良い点と注意点まとめ

# はじめに

10連休のひまなときにこつこつコンバートを行い、
自作のJava製Androidアプリ(約300ファイル)をフルKotlin化しました。

↓Kotlin100%の図
<img width="640" alt="スクリーンショット 2019-05-03 2.11.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/49c9c5e8-5b4f-3ea2-0ac1-49fa06afe602.png">

ちょうど10連休明けに行われたGoogle I/O 2019では[Kolinファースト](https://developers-jp.googleblog.com/2019/05/google-io-2019-android-play.html)が表明され
JavaからKotlinへ変換する機会が増えそうなのでつまづいたこととその回避方法をまとめます。

環境はAndroidStudio 3.4です。

# 変換方法

すべて手作業で行うのが確実ですがAndroidStudioに変換コマンドが用意されています。
「メニュー > Code > Convert Java File to Kotlin File」
ショートカットは「Shift + Option + Command + k」です。

<img width="640" alt="スクリーンショット 2019-05-14 7.58.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/3b6e7af0-917d-4004-c111-439e1e37ea25.png">


# Kotlin変換コマンドの注意点

## コメント

地味にコメントが変換を妨げる事がありました。
コメントアウトした箇所はKotlin変換後もJavaのままになります。
また、Kotlinではコメントを開く `/*` の数とコメントを閉じる `*/` の数が揃っている必要があります。

【変換前サンプル】
<img width="640" alt="スクリーンショット 2019-05-13 22.33.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/897c6939-9085-5797-6eb0-8250dd363858.png">

【変換後サンプル】

- コメント部分がJavaのまま
- コメントの閉じる数が足りずビルドエラー

<img width="640" alt="スクリーンショット 2019-05-13 22.32.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/928a5226-462e-22b2-1bcd-156ea24e7d8f.png">

## Nullable / NonNullが曖昧なもの

KotlinはNullSafeな言語仕様のため、Java側で Nullable / NonNullが曖昧な場合、
プラットフォーム型(NullableかもしれないしNonNullかもしれない型)として扱われます。
NonNullが決定的でない限りNullableとして扱うほうが安全です。

実際に実行時エラーになったコードは以下です。
変数の後に `!`がつくものはプラットフォーム型でコード上はNonNullとして扱えたのですが
実行時にNullが入ってきてエラーになることがあります。
<img width="640" alt="スクリーンショット 2019-05-09 3.47.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/b17c1ee8-50b9-34f7-22fe-32d7538cb1d0.png">

いったんNullable扱いとして早期リターンなどすると次の行からNonNullとして扱うことができます。
<img width="640" alt="スクリーンショット 2019-05-09 3.47.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/7ff298f7-70ba-fbad-3d4a-754fcd5ae855.png">


### Javaで定義されたクラスのサブクラスをKotlinで定義する場合

Nullable/NonNull関連では、Kotlin変換コマンドを使用した場合、
特に以下の例外が発生しやすかったです。

```terminal
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: 
```

これはスーパークラスがJavaでサブクラスがKotlinの場合でnon-nullとnullの扱いを誤った場合に発生する例外です。
例えばAppCompatActivityを継承した以下の場合に例外が発生します。
実行時エラーなので、コードは正常にビルドできてしまいます。

```kotlin
class MainActivity : AppCompatActivity() {
:
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        :
```

以下のように `Bundle?` を引数の型とするのが正しいコードです。

```kotlin
class MainActivity : AppCompatActivity() {
:
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        :
```

これはスーパークラス側のコードを読まないと判明しづらいです。
実際のコードを読むとBundleに `@Nullable` アノテーションがついており
 `Bundle` でなく `Bundle?` が正しいことがわかります。

```androidx-appcompat:1.0.2@AppCompatActivity.java
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
```

# Kotlin変換前にやると良い点

上記の注意点を踏まえて、変換前にやると良いことを挙げます。

## Kotlin変換に関するドキュメントを読む

Kotlin公式にもAndroid公式にもJavaとの相互運用ドキュメントがあります。

- https://kotlinlang.org/docs/reference/java-interop.html
- https://developer.android.com/kotlin/interop


## NonNullアノテーションを活用する

Javaの時点でできる限りNonNullを区別したほうが良いです。
Kotlinドキュメントによると以下のアノテーションに対応しています。

https://kotlinlang.org/docs/reference/java-interop.html

> JetBrains (@Nullable and @NotNull from the org.jetbrains.annotations package)
> Android (com.android.annotations and android.support.annotations)
> JSR-305 (javax.annotation, more details below)
> FindBugs (edu.umd.cs.findbugs.annotations)
> Eclipse (org.eclipse.jdt.annotation)
> Lombok (lombok.NonNull).

### AndroidXに対応する

旧サポートライブラリを使用している場合はAndroidXに移行後に変換を行ったほうが良いです。
AndroidXのほうがNonNull, Nullableがしっかり判別されているのでKotlinへの変換リスクが少ないです。
以下RecyclerViewのJavaコードの比較です。

旧サポートライブラリのRecyclerView
https://android.googlesource.com/platform/frameworks/support/+/121ba96/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java#5143

```java
        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
```

AndroidX ライブラリのRecyclerView
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java#6888

```java
        @NonNull
        public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
```

## ユニットテストで動作保証する

Java時点でユニットテストを追加すればKotlin変換後に動作確認が容易です。
最低限でも良いのでユニットテストを追加することをおすすめします。

## Lintを活用する

Editer > Inspectionで"interop"などで検索するとKotlin変換に活用できる相互運用系のLintが表示されます。
チェックをつけると変換前後に危ういコードが警告されます。

https://developer.android.com/kotlin/interop#lint_checks
https://developer.android.com/studio/write/lint?hl=ja

<img width="640" alt="スクリーンショット 2019-05-13 23.04.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/ac3f06ca-e16b-9689-f6b6-ea9de6a6c1c8.png">


## Kotlinの予約語の変数名を避ける

is, object, whenなどのKotlin予約語や、内部のJavaコードで使用されるget, setなどは予めリネームすると良いです。
一応Kotlinでもバッククオートで囲めば使用できますが避けたほうが良いです。

# Kotlin変換後にやると良い点

Kotlin変換コマンドに生成されたKotlinコードはKotlinを活用しているとは言い難いコードです。
最低限、警告が出ずある程度安全な状態にする方法を挙げます。

## Nullable型のアンラップ方法を検討する

変換コマンド後のコードでは基本的に `!!` を利用してNonNullにアンラップしています。
 `?.`などで安全にアンラップできないか検討します。

Kotlin変換直後のコード例

```kotlin
    var webView: WebView? = null

    fun load() {
        if (webView != null) {
            webView!!.load()
        }
        :
```

`?.`を活用する例

```kotlin
    var webView: WebView? = null

    fun load() {
        webView?.load()
        :

```


## varの扱いは適切か検討する

Nullableのプロパティは `lateinit` や `by lazy` など活用できるか検討します。
View関連で `Activity#onCreate` など `init` 以外で初期化する場合は `lateinit` を利用するとNonNullのプロパティとして扱うことができます。

```kotlin
    private var mRecyclerView: RecyclerView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
    :
        mRecyclerView = findViewById(R.id.recycler_view)
```

```kotlin
    private lateinit var mRecyclerView: RecyclerView

    override fun onCreate(savedInstanceState: Bundle?) {
    :
        mRecyclerView = findViewById(R.id.recycler_view)
```

## 定数valはconst valにできないか検討する

Javaでstatic finalの定数の場合、Kotlin変換コマンド後のコードではvalとして扱われます。
const valにできないか検討します(通常、Lintに従えばconstを付与することになります)

```kotlin
    companion object {
        private val TAG = "MainActivity"
```

```kotlin
    companion object {
        private const val TAG = "MainActivity"
```

## 仕上げにFirebase Robo Testを活用する

変換後、テストコードをちゃんと書いたつもりでも自分の予想の範囲外のバグが潜んでいることがあります。
第三者やロボにテストをさせることで防ぐことができます。
Nullable / NonNull / Platform と型が増え、初期化前のlateinitなど状態も増えています。
Firebase Robo Testを使用して「うっかりミス」を防ぐことをおすすめします。

<img width="640" alt="スクリーンショット 2019-05-13 22.50.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19692/be986c5d-3083-58b1-079c-835f55ab36e0.png">
↑自分のRobo Testで判明した実行時エラー。初期化前にlateinitにアクセスしてしまったミス。

# 100%Kotlinの感想
Kotlin自体はスッキリするけどKotlin⇔Javaの橋渡しでエラーが起きがち。
けっこう茨の道なので事前に治安の良いJavaコードにしておいたほうが良さげ。
いったん変換してしまえばCoroutineや拡張関数など便利な機能が使えて生活が変わります。