Java
Android
Kotlin
AndroidStudio
AndroidX

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


はじめに

10連休のひまなときにこつこつコンバートを行い、

自作のJava製Androidアプリ(約300ファイル)をフルKotlin化しました。

↓Kotlin100%の図

スクリーンショット 2019-05-03 2.11.20.png

ちょうど10連休明けに行われたGoogle I/O 2019ではKolinファーストが表明され

JavaからKotlinへ変換する機会が増えそうなのでつまづいたこととその回避方法をまとめます。

環境はAndroidStudio 3.4です。


変換方法

すべて手作業で行うのが確実ですがAndroidStudioに変換コマンドが用意されています。

「メニュー > Code > Convert Java File to Kotlin File」

ショートカットは「Shift + Option + Command + k」です。

スクリーンショット 2019-05-14 7.58.17.png


Kotlin変換コマンドの注意点


コメント

地味にコメントが変換を妨げる事がありました。

コメントアウトした箇所はKotlin変換後もJavaのままになります。

また、Kotlinではコメントを開く /* の数とコメントを閉じる */ の数が揃っている必要があります。

【変換前サンプル】

スクリーンショット 2019-05-13 22.33.11.png

【変換後サンプル】


  • コメント部分がJavaのまま

  • コメントの閉じる数が足りずビルドエラー

スクリーンショット 2019-05-13 22.32.52.png


Nullable / NonNullが曖昧なもの

KotlinはNullSafeな言語仕様のため、Java側で Nullable / NonNullが曖昧な場合、

プラットフォーム型(NullableかもしれないしNonNullかもしれない型)として扱われます。

NonNullが決定的でない限りNullableとして扱うほうが安全です。

実際に実行時エラーになったコードは以下です。

変数の後に !がつくものはプラットフォーム型でコード上はNonNullとして扱えたのですが

実行時にNullが入ってきてエラーになることがあります。

スクリーンショット 2019-05-09 3.47.20.png

いったんNullable扱いとして早期リターンなどすると次の行からNonNullとして扱うことができます。

スクリーンショット 2019-05-09 3.47.38.png


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

Nullable/NonNull関連では、Kotlin変換コマンドを使用した場合、

特に以下の例外が発生しやすかったです。

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

これはスーパークラスがJavaでサブクラスがKotlinの場合でnon-nullとnullの扱いを誤った場合に発生する例外です。

例えばAppCompatActivityを継承した以下の場合に例外が発生します。

実行時エラーなので、コードは正常にビルドできてしまいます。

class MainActivity : AppCompatActivity() {

:
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
:

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

class MainActivity : AppCompatActivity() {

:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
:

これはスーパークラス側のコードを読まないと判明しづらいです。

実際のコードを読むとBundleに @Nullable アノテーションがついており

Bundle でなく Bundle? が正しいことがわかります。


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との相互運用ドキュメントがあります。


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

        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

        @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

スクリーンショット 2019-05-13 23.04.38.png


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

is, object, whenなどのKotlin予約語や、内部のJavaコードで使用されるget, setなどは予めリネームすると良いです。

一応Kotlinでもバッククオートで囲めば使用できますが避けたほうが良いです。


Kotlin変換後にやると良い点

Kotlin変換コマンドに生成されたKotlinコードはKotlinを活用しているとは言い難いコードです。

最低限、警告が出ずある程度安全な状態にする方法を挙げます。


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

変換コマンド後のコードでは基本的に !! を利用してNonNullにアンラップしています。

?.などで安全にアンラップできないか検討します。

Kotlin変換直後のコード例

    var webView: WebView? = null

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

?.を活用する例

    var webView: WebView? = null

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


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

Nullableのプロパティは lateinitby lazy など活用できるか検討します。

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

    private var mRecyclerView: RecyclerView? = null

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

    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を付与することになります)

    companion object {

private val TAG = "MainActivity"

    companion object {

private const val TAG = "MainActivity"


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

変換後、テストコードをちゃんと書いたつもりでも自分の予想の範囲外のバグが潜んでいることがあります。

第三者やロボにテストをさせることで防ぐことができます。

Nullable / NonNull / Platform と型が増え、初期化前のlateinitなど状態も増えています。

Firebase Robo Testを使用して「うっかりミス」を防ぐことをおすすめします。

スクリーンショット 2019-05-13 22.50.47.png

↑自分のRobo Testで判明した実行時エラー。初期化前にlateinitにアクセスしてしまったミス。


100%Kotlinの感想

Kotlin自体はスッキリするけどKotlin⇔Javaの橋渡しでエラーが起きがち。

けっこう茨の道なので事前に治安の良いJavaコードにしておいたほうが良さげ。

いったん変換してしまえばCoroutineや拡張関数など便利な機能が使えて生活が変わります。