Apollo Kotlinをv4にバージョンアップしました
この記事は KINTOテクノロジーズアドベントカレンダー2024 の22日目の記事です🎅🎄
はじめまして!
KINTOテクノロジーズでUnlimited(Android)アプリを開発しているkikumidoと申します。
弊アプリでは、GraphQLクライアントとしてApollo Kotlinを使用しています。
Apollo Kotlin v4では、パフォーマンスの向上や新機能の追加など、多くの改善が行われています。
これらの利点を活かすため、私たちはv3からv4へのアップグレードを決定しました。
本記事では、今年の7月にリリースされたApollo Kotlin v4への移行作業の流れと、その過程で遭遇した課題について詳しく説明します。
当初はスムーズな移行を期待していましたが、予想外の例外に直面し、解決策を見出すのに苦労する場面もありました。
この記事が、これからバージョンアップを検討している方々にとって、有益な情報源となれば幸いです。
v3からv4への移行
公式サイトに従い対応していきます。
尚、Android Studioにプラグインをインストールすることで、半自動でバージョンアップすることも出来ます。
必要な対応を確認しながら移行作業を実施したかったので、私はプラグインを使わずに愚直に手動で対応しました。
一方、プラグインでどこまで自動でできるのかにも興味があったので、手動の場合との作業方法の比較を後半に記載します。
1.対応必須箇所
v3からv4への移行で、弊アプリでAndroid Studio上でエラーを無くす為に必須だった対応は5つあります。
以下に対応内容をひとつずつ記載していきます。
1.1.ライブラリのバージョンアップ
何はともあれ、まずはライブラリのバージョンアップをします。
apollographql = "3.8.5"
apollo-runtime = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollographql" }
apollographql-apollo = { id = "com.apollographql.apollo3", version.ref = "apollographql" }
↓
// 記事執筆時はv4.1.0がリリースされていますが、移行当時の最新である4.0.1で記載しています
apollographql = "4.0.1"
apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollographql" }
apollographql-apollo = { id = "com.apollographql.apollo", version.ref = "apollographql" }
ふむふむ。com.apollographql.apollo3
がcom.apollographql.apollo4
になったのではなく、「3」が消えましたね。
1.2.gradleファイルの修正
v3の時は不要でしたが、v4ではserviceで囲う必要があります。
apollo {
packageName.set("com.example.xxx.xxx.xxx")
・・・
}
↓
apollo {
service("service") {
packageName.set("com.example.xxx.xxx.xxx")
・・・
}
}
1.3.importの変更
先述の「3」が消えた時に予想できましたが、importのパッケージ名を変更する必要があります。
「3」を消すだけです。
import com.apollographql.apollo3.*
↓
import com.apollographql.apollo.*
1.4.例外処理を修正
execute()
でフェッチエラーがスローされないように変更になりましたので対応します。
プロジェクトごとに対応方針が異なると思いますが、私たちは一旦既存処理への影響を最小限に抑える対応を実施することにしました。
v3の時にフェッチエラーが発生しApolloException
をスローしていたのと同じ条件の時に、DefaultApolloException
をスローするように修正する方法です。
共通処理を修正することにより、共通処理を呼び出している側では実装を修正することなく、既存と同じ挙動にできました。
apolloClient.query(query).execute()
apolloClient.mutation(mutation).execute()
↓
execute(apolloClient.query(query))
execute(apolloClient.mutation(mutation))
private suspend inline fun <D : Operation.Data> execute(apolloCall: ApolloCall<D>): ApolloResponse<D> {
val response = apolloCall.execute()
if (response.data == null) {
response.exception?.let { // response.dataがnullかつresponse.exceptionがnullでない場合はフェッチエラー
throw DefaultApolloException(it.message, it.cause)
}
}
return response
}
既存実装やv4対応の方針により一気に移行することが難しい場合は、v3の挙動を変えずに移行する為のヘルパーとしてexecuteV3()
が準備されていますので、そちらに一旦置き換えることも可能です。
apolloClient.query(query).executeV3()
apolloClient.mutation(mutation).executeV3()
機能毎など、徐々にv4への移行を進めたい場合はこちらを使うとよさそうです。
1.5.ApolloExceptionをDefaultApolloExceptionに修正
ApolloException
がsealed class
になったため、インスタンスを生成していた箇所をDefaultApolloException
に置き換えます。
Android Studio上でエラーを無くすために必要な対応は以上となります。
2.ビルド
エラーも無くなったので・・・・
それでは、待ちに待ったビルドを実行してみましょう!
ダララララララララララララララララララララララララララララララララ(ドラムロール)・・・ジャン!
はい!出ました!
ビルドエラー・・・!
・・・まあ、そんなにすんなり行くとは思っていませんでしたが・・・地味にショック・・・
さて、気を取り直してエラーログを確認していきます。
2.1.エラーログを確認
見慣れないエラーログが大量に吐かれていました。
要するに「KSP[1]でAssistedInjectProcessingStep[2]の時に必要なClassがないよ」ということらしい。
If type 'error.NonExistentClass' is a generated type, check above for compilation errors that may have prevented the type from being generated. Otherwise, ensure that type 'error.NonExistentClass' is on your classpath.
e: [ksp] AssistedInjectProcessingStep was unable to process 'XXXXXViewModel(java.lang.String,long,com.xx.xx.XXXXXRepository)' because 'error.NonExistentClass' could not be resolved.
もちろんソース上は存在するクラスですし、本対応前は問題なくビルドが成功していました。
v4移行で必要な対応が漏れていないか?など色々調べましたがなかなかわからず・・・
色々調査した結果、下記3種類の方法でビルドを成功させることができました。
- A.
Hilt
をKSP
からkapt
に戻す - B. build.gradle.ktsに追記する:パターン1
androidComponents {
onVariants(selector().all()) { variant ->
afterEvaluate {
val variantName = variant.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
val generateTask = project.tasks.findByName("generateServiceApolloSources")
val kspTask = project.tasks.findByName("ksp${variantName}Kotlin")
as? org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool<*>
kspTask?.run {
generateTask?.let {
setSource(it.outputs)
}
}
}
}
}
- C. build.gradle.ktsに追記する:パターン2
apollo {
service("service") {
packageName.set("com.example.xxx.xxx.xxx")
// 追記start
outputDirConnection {
connectToAndroidSourceSet("main")
}
// 追記end
・・・
}
}
ちなみに
Hilt
のAssistedInject
を使用している(定義方法によっては問題ない場合もあるようです)Hilt
にKSP
を使用している- 先述の「1.2.gradleファイルの修正」で
service
で囲う
上記の条件が揃っていればv3でも再現しますので厳密には「v4対応」とは言えませんが、同時に対応したので記載しておきます。
弊アプリでは最初「B」で対応していましたが、ApolloExtensionのservice
の不具合[3]で「C」で対応できることがわかった為、「C」を採用しました。
以上でビルドが成功し、アプリが起動・既存と同じ挙動をするようv4へ移行することが出来ました!
3.非推奨箇所の対応
続いて、非推奨でワーニングとなった以下の2つに対応します。
3.1.ApolloResponse.Builderを修正
@Deprecated("Use 2 params constructor instead", ReplaceWith("Builder(operation = operation, requestUuid = requestUuid).data(data = data)"))
とのことなので、その通りに修正します。
ApolloResponse.Builder(operation(), UUID_CONST, data).build()
↓
ApolloResponse.Builder<D>(operation(), UUID_CONST).data(data).build()
data
が外に出ましたね。
Builderパターンに則った形式に変更されたようです。
3.2.Errorインスタンス生成処理をBuilderに修正
Errorインスタンス生成でコンストラクタを使用していた箇所を、Builderを使用するように修正します。
Error("occurred Error", null, null, mapOf("errorCode" to responseCode), null)
↓
Error.Builder(message = "occurred Error")
.putExtension("errorCode", responseCode)
.build()
以上でワーニングを消すことも出来ました。
4.プラグインでの移行
Android StudioでApolloのプラグインを使用することによりある程度自動で移行してくれます。
実行方法はとても簡単で、
Android StudioでApolloのプラグインをインストールし
Tools > Apollo > Migrate to Apollo Kotlin 4...
をタップするだけです。簡単ですね。
では実行結果を確認します。
先述の
- 1.1.ライブラリのバージョンアップ
- 1.2.gradleファイルの修正
- 1.3.importの変更
は自動で移行してくれました。
- 1.4.例外処理を修正
はexecuteV3()
に置き換えることしか行われないので、v4として適切な対応は手動で行う必要があります。
- 1.5.ApolloExceptionをDefaultApolloExceptionに修正
は移行してくれませんでしたので、対応は手動で行う必要があります。
文字通り、機械的にできる箇所は自動で移行してくれるので、
プラグインを使用して移行した上で、必要な箇所のみ手動で対応する方法もありだと思います。
ちなみにこのプラグインは未使用フィールドをグレー表示してくれたり、移行時以外も活躍してくれるので、インストールしておくと良いと思います。
5.まとめ
Apollo Kotlinをv3からv4へ移行した時の話は以上になります。
実際に対応した時から時間が経過してしまったので「すでに対応済み」という方も多くいらっしゃると思いますが、何かしらお役に立つものがあると嬉しいです。
最後までお読みいただきありがとうございました。
6. 関連リンク
関連記事 | Related Posts
We are hiring!
【プロジェクトマネージャー】モバイルアプリ開発G/大阪
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。
【iOSエンジニア】モバイルアプリ開発G/大阪
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。