KINTO Tech Blog
Android

Apollo Kotlinをv4にバージョンアップしました

Cover Image for 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.ライブラリのバージョンアップ

何はともあれ、まずはライブラリのバージョンアップをします。

libs.versions.toml(v3)
apollographql = "3.8.5"
apollo-runtime = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollographql" }
apollographql-apollo = { id = "com.apollographql.apollo3", version.ref = "apollographql" }

libs.versions.toml(v4)
// 記事執筆時は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.apollo3com.apollographql.apollo4になったのではなく、「3」が消えましたね。

1.2.gradleファイルの修正

v3の時は不要でしたが、v4ではserviceで囲う必要があります。

build.gradle.kts(v3)
apollo {
    packageName.set("com.example.xxx.xxx.xxx")
    ・・・
}

build.gradle.kts(v4)
apollo {
    service("service") {
        packageName.set("com.example.xxx.xxx.xxx")
        ・・・
    }
}

1.3.importの変更

先述の「3」が消えた時に予想できましたが、importのパッケージ名を変更する必要があります。
「3」を消すだけです。

kotlinファイル(v3)
import com.apollographql.apollo3.*

kotlinファイル(v4)
import com.apollographql.apollo.*

1.4.例外処理を修正

execute()でフェッチエラーがスローされないように変更になりましたので対応します。

プロジェクトごとに対応方針が異なると思いますが、私たちは一旦既存処理への影響を最小限に抑える対応を実施することにしました。
v3の時にフェッチエラーが発生しApolloExceptionをスローしていたのと同じ条件の時に、DefaultApolloExceptionをスローするように修正する方法です。
共通処理を修正することにより、共通処理を呼び出している側では実装を修正することなく、既存と同じ挙動にできました。

ApiClient共通クラス(v3)
apolloClient.query(query).execute()

apolloClient.mutation(mutation).execute()

ApiClient共通クラス(v4)
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()が準備されていますので、そちらに一旦置き換えることも可能です。

ApiClient共通クラス(v4でv3の挙動のままにする)
apolloClient.query(query).executeV3()

apolloClient.mutation(mutation).executeV3()

機能毎など、徐々にv4への移行を進めたい場合はこちらを使うとよさそうです。

1.5.ApolloExceptionをDefaultApolloExceptionに修正

ApolloExceptionsealed 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. HiltKSPからkaptに戻す
  • B. build.gradle.ktsに追記する:パターン1
build.gradle.kts(v4)
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
build.gradle.kts(v4)
apollo {
    service("service") {
        packageName.set("com.example.xxx.xxx.xxx")
        // 追記start
        outputDirConnection { 
            connectToAndroidSourceSet("main") 
        }
        // 追記end
        ・・・
    }
}

ちなみに

  • HiltAssistedInjectを使用している(定義方法によっては問題ない場合もあるようです)
  • HiltKSPを使用している
  • 先述の「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)"))

とのことなので、その通りに修正します。

kotlinファイル(v3)
ApolloResponse.Builder(operation(), UUID_CONST, data).build()

kotlinファイル(v4)
ApolloResponse.Builder<D>(operation(), UUID_CONST).data(data).build()

dataが外に出ましたね。
Builderパターンに則った形式に変更されたようです。

3.2.Errorインスタンス生成処理をBuilderに修正

Errorインスタンス生成でコンストラクタを使用していた箇所を、Builderを使用するように修正します。

kotlinファイル(v3)
Error("occurred Error", null, null, mapOf("errorCode" to responseCode), null)

kotlinファイル(v4)
Error.Builder(message = "occurred Error")
    .putExtension("errorCode", responseCode)
    .build()

以上でワーニングを消すことも出来ました。

4.プラグインでの移行

Android StudioでApolloのプラグインを使用することによりある程度自動で移行してくれます。
実行方法はとても簡単で、
Android StudioでApolloのプラグインをインストールし
Tools > Apollo > Migrate to Apollo Kotlin 4...
をタップするだけです。簡単ですね。
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. 関連リンク

https://www.apollographql.com/docs/kotlin/migration/4.0
https://www.apollographql.com/docs/kotlin/testing/android-studio-plugin
https://developer.android.com/build/migrate-to-ksp?hl=ja

脚注
  1. 弊アプリでは、KSPを使用しています ↩︎

  2. 弊アプリでは、依存関係インジェクションライブラリとしてHiltを使用しています ↩︎

  3. Issues → 記事執筆時最新のv4.1.0でも再現しました ↩︎

Facebook

関連記事 | Related Posts

We are hiring!

【プロジェクトマネージャー】モバイルアプリ開発G/大阪

モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。

【iOSエンジニア】モバイルアプリ開発G/大阪

モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。

イベント情報

【さらに増枠】AWSコミュニティHEROと学ぶ!Amazon Bedrock勉強会&事例共有会
製造業でも生成AI活用したい!名古屋LLM MeetUp#4