Android: kotshi から moshi-kotlin-codegen に移行する
moshi の kotlin 用ライブラリ kotshi に依存しているのを消したいなーと思いまして、その時の対応メモです。
単純に kotshli をなくしてもいいのですが、それだと、moshi-kotlin による動的なクラスロードに依存してしまい重くなる(らしい)。
moshi 公式で moshi-kotlin-codegen というコンパイル時にkaptで解決してくれるライブラリがあり、これに置き換えれば万事解決しそうです。
手順
- build.gradle で debugImplementation
moshi-kotlin
/ kaptReleasemoshi-kotlin-codegen
にする。 -
@se.ansman.kotshi.JsonSerializable
を@com.squareup.moshi.JsonClass(generateAdapter = true)
に変更する。@field:Json
を@Json
に置き換える。 - デバッグ時のみ、
KotlinJsonAdapterFactory
をMoshi.Builder()
に追加する。
1.build.gradleの設定変更
変更前
val kotshiVersion = "2.6.3"
implementation("se.ansman.kotshi:api:$kotshiVersion")
kapt("se.ansman.kotshi:compiler:$kotshiVersion")
val moshiVersion = "1.12.0"
implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion")
変更後
moshi 1.12 は kotlin 1.6系で kaptビルドエラーになる不具合があるため、 moshi1.13 にアップデートしました。
val moshiVersion = "1.13.0" // kotlin1.6でのmoshi不具合解消のためアップデート
implementation("com.squareup.moshi:moshi:$moshiVersion")
// デバッグ時はビルド速度優先で moshi-kotlinを使い、リリース時は実行パフォーマンス向上のため moshi-kotlin-codegen を使う
debugImplementation("com.squareup.moshi:moshi-kotlin:$moshiVersion")
kaptRelease("com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion")
2. JSONシリアライズ・デシリアライズ対象クラスのアノテーションを設定
変更前
import com.squareup.moshi.Json
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class User(
@field:Json(name = "user_id")
val id: Int,
val name: String,
)
変更後
proguard の難読化を避けるため、 generateAdapter = true とします。
また、@field:Json は解釈されないため、 @Json に変更しました。
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "user_id")
val id: Int,
val name: String,
)
3. Moshi.Builder の設定変更
kotshi用のApplicationJsonAdapterFactoryを削除しました。
また、moshi-kotlin-codegenに存在しない KotlinJsonAdapterFactory を動的ロードに変更してビルドエラーを避けています。
変更前
return Moshi.Builder()
.add(ApplicationJsonAdapterFactory()) // kotshi用(削除)
.add(KotlinJsonAdapterFactory()) // moshi-kotlin用
.build()
変更後
- デバッグ時だけ moshi-kotlin を使う
val builder = Moshi.Builder()
if (BuildConfig.DEBUG) {
// デバッグ時は moshi-kotlin を使う。
val instance = Class.forName("com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory").newI
nstance() as JsonAdapter.Factory
builder.add(instance)
}
return builder.build()
- 別解: クラスロードできるときだけmoshi-kotlinを使う。
gradleだけで設定切り替えできるのでこちらのほうが便利。
ただし、SDKでの提供時など、外部モジュールが moshi-kotlinをロードする可能性がある場合は引きずられてしまうことに注意。
val builder = Moshi.Builder()
// moshi-kotlin 対応時は KotlinJsonAdapterFactory を差し込む。
try {
val factory = Class.forName("com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory").newInstance() as JsonAdapter.Factory
builder.add(factory)
} catch (_: ClassNotFoundException) {
}
return builder.build()
余談。はまった所
すでに kapt を使っているモジュールのgradleで、 kotlin("kapt")
を使ったらリリースビルド時だけJSON解決できない→Retrofit2 でAPIコールできない、というので丸一日ハマりました。
結論としては、 kotlin("kapt")
や id("kotlin-kapt")
はメンテナンスされてなくて古いので、
id("org.jetbrains.kotlin.kapt")
を使いましょう。という話でした。
kotlin-なんとか
みたいなプラグインの命名規則がわかりづらく、mavenの group:artifact:version
表記にしましょう、ということのようで・・・。
参考: build.gadle* に 'android-library' や 'kotlin-kapt' があったらやばい!
具体的には hiltのkaptがすでにあるモジュールの build.gradle.kts で、
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("dagger.hilt.android.plugin")
}
となっていて、
val jsonAdapter = moshi.adapter(VerificationInfoEntity::class.java)
Log.d(TAG, "jsonAdapter=$jsonAdapter")
としても、リリースビルド時だけ動かない。
kaptRelease("com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion")
でなく
kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion")
としてデバッグビルド時もkapt有効にしても問題なく動く。minifyも関係ない。という謎現象に苦しんでました。
kotlin("kapt")
とか id("kotlin-kapt")
、
kotlin("android")
とか id("kotlin-android")
のような、古い書式のpluginは使わないようにしよう、と心に誓ったドハマリでした…。
Discussion