概要
本記事では、Apollo GraphQL Kotlin Clientの概要と使い方について紹介します。特に、以下のチュートリアルについてまとめます。
Introduction to Apollo Kotlin Tutorial)
なお、本記事はチュートリアルを踏まえた情報のまとめとなっております。このため、チュートリアルを読まれることを推奨します。
Apollo Kotlinとは
Apollo GraphQL Kotlin Clientは、GraphQLクライアントです。GraphQLに対応したAPIの場合、単一のエンドポイントから必要なデータを柔軟に取得できます。さらに、設定ファイル .graphqls を用いることで、クライアントから使用するモデルクラスを自動生成してくれます。このため、JSONのパースやモデルクラスへのマッピングを行う必要がなく、開発効率が向上します。当然ながら、APIがGraphQLに対応している必要があります。
詳しくは、公式GitHubリポジトリを参照してください。
GraphQLとは
GraphQLは、Web APIの設計手法の1つです。従来のREST APIに比べ、以下のような特徴があります。
GraphQL | REST API | 備考 |
---|---|---|
データのフェッチに必要なリクエスト数が少なくなる | 複数のエンドポイントが必要なため、データのフェッチに必要なリクエスト数が増える |
「アンダーフェッチがない」って言い方をしたりする |
必要なデータだけを取得できるため、レスポンスサイズが小さくなる | 一度に大量のデータを取得することができるため、レスポンスサイズが大きくなる可能性がある | 「オーバーフェッチがない」って言い方をしたりする |
単一エンドポイントで複数のデータを要求できる | エンドポイントごとに異なるURLを使用する必要があり、複数のリクエストが必要になる | |
APIから必要なデータを柔軟に取得できる | APIから取得できるデータは事前に定義されており、必要なデータをすべて取得する必要がある |
|
サーバー側で定義されたスキーマに基づいて動的にデータを要求できる | APIエンドポイントが変更された場合、コード内のエンドポイント情報も変更する必要がある |
シンプルにまとめると「GraphQLは柔軟で、効率的で、使いやすいAPIを提供するために設計されている。」といったイメージです。
データ授受のイメージ
APIのリクエストとレスポンスの形式を所定のファイルに定義するとJsonが返ってきます
// イメージ
{
hero {
name
}
}
↓ 以下形式でJsonレスポンスが取得
{
"hero": {
"name": "Luke Skywalker"
}
}
フィールドを追加すると必要な情報だけ取ってこれる。
{
hero {
name
height
}
}
↓
// json
{
"hero": {
"name": "Luke Skywalker",
"height": 1.72,
}
}
GraphQLクライアントはここからさらにModelクラスをつくってくれる
所定のファイル = graphqls ファイルでスキーマを定義、graphqlで実行クエリを定義
// hoge.graphqls
type SampleQuery {
hero: Hero
}
type Hero {
name: String!
height: Int!
}
schema {
query: SampleQuery
}
// hoge.graphql
query getHero {
hero {
name
}
}
↓
//
// AUTO-GENERATED FILE. DO NOT MODIFY.
//
// This class was automatically generated by Apollo GraphQL version '3.7.5'.
//
package com.example.rocketreserver
import ...
public class GetHeroQuery() : Query<GetHeroQuery.Data> {
public override fun equals(other: Any?): Boolean = other != null && other::class == this::class
public override fun hashCode(): Int = this::class.hashCode()
public override fun id(): String = OPERATION_ID
public override fun document(): String = OPERATION_DOCUMENT
public override fun name(): String = OPERATION_NAME
public override fun serializeVariables(writer: JsonWriter,
customScalarAdapters: CustomScalarAdapters): Unit {
// This operation doesn't have any variable
}
public override fun adapter(): Adapter<Data> = GetHeroQuery_ResponseAdapter.Data.obj()
public override fun rootField(): CompiledField = CompiledField.Builder(
name = "data",
type = SampleQuery.type
)
.selections(selections = GetHeroQuerySelections.__root)
.build()
@ApolloAdaptableWith(GetHeroQuery_ResponseAdapter.Data::class)
public data class Data( // Model データ
public val hero: Hero?,
) : Query.Data
public data class Hero( // Model データ
public val name: String,
)
public companion object {
public const val OPERATION_ID: String =
"b15cc8a0721be79e5311944346bb73770d1c84d0a0cac560e2b69c29cb993e7b"
/**
* The minimized GraphQL document being sent to the server to save a few bytes.
* The un-minimized version is:
*
* query getHero {
* hero {
* name
* }
* }
*/
public val OPERATION_DOCUMENT: String
get() = "query getHero { hero { name } }"
public const val OPERATION_NAME: String = "getHero"
}
}
GetHeroQuery.Data
型のModelクラスからHeroが取れる。
GraphQL 用語
Tutorial に出てくる用語をまとめると
スキーマ定義
-
Objects
GraphQLスキーマに定義するオブジェクトのタイプです。クライアントがフィールドを指定するために使用されます。 -
Scalars
GraphQLに組み込まれているスカラータイプ。Int、String、Float、Boolean、IDが含まれます。カスタムスカラーを定義することもできます。 -
Enums
GraphQLスキーマに定義する列挙型です。列挙型は、GraphQLスキーマに対して固定された値を持つフィールドを作成するために使用されます。
クエリの実行
-
Query
クライアントがデータを取得するために使用するメソッドです。フィールドの一覧を指定して、返されるデータの構造を定義します。 (RestでいうGet -
Mutation
クライアントがデータを変更するために使用するメソッドです。クライアントが送信する変更を指定して、返されるデータの構造を定義します。 (RestでいうPostやPatch -
Subscription
クライアントがデータの変更を受け取るために使用するメソッドです。リアルタイムで更新されるデータを取得するために使用されます。 (WebSockts
スキーマ定義に関する特殊な用語
- Directives
スキーマの実行方法に影響を与える指令です。例えば、特定のフィールドを省略したり、フィールドを変形したり、特定のスキーマのフィールドのアクセスを制限することができます。
サンプル実装
必要なことをたらたら書きます。
まずgradle
plugin {
id("com.android.application")
// ...
id("com.apollographql.apollo3").version("anyversion")
}
apollo {
service("service") {
packageName.set("com.example.rocketreserver") // package名を指定する
}
}
dependencies{
....
implementation("com.apollographql.apollo3:apollo-runtime:anyversion")
}
前述の設定ファイルであるhoge.graphqls, hoge.graphql( サンプル ) は以下フォルダに格納します。
その位置を示すため上記ではパッケージ名を指定してます。
{module}/src/main/graphql
クライアントクラスはOkHttpのお作法と同じです。
val apolloClient = ApolloClient.Builder()
.serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/graphql")
.webSocketServerUrl("wss://apollo-fullstack-tutorial.herokuapp.com/graphql")
.okHttpClient(
OkHttpClient.Builder()
.addInterceptor(AuthorizationInterceptor())
.build()
)
.webSocketReopenWhen { throwable, attempt ->
Log.d("Apollo", "WebSocket got disconnected, reopening after a delay", throwable)
delay(attempt * 1000)
true
}
.build()
class AuthorizationInterceptor() : Interceptor ...
呼び方
- Queryリクエスト
response = apolloClient.query(LaunchListQuery(Optional.present(cursor))).execute()
LaunchListQuer
はgraphqlから作られたQuery実行用のクラス。
Optional.present(cursor)
はリクエスト時に設定するパラメータ。
レスポンス型 -> ApolloResponse<LaunchListQuery.Data>?
- Mutationリクエスト
apolloClient.mutation(LoginMutation(email = email)).execute()
LoginMutation
はgraphqlから作られたMutation実行用のクラス。
レスポンス型 -> ApolloResponse<LoginMutation.Data>
- Subscription
val tripBookedFlow = remember { apolloClient.subscription(TripsBookedSubscription()).toFlow() }
val tripBookedResponse: ApolloResponse<TripsBookedSubscription.Data>? by tripBookedFlow.collectAsState(initial = null)
LaunchedEffect(tripBookedResponse) {
if (tripBookedResponse == null) return@LaunchedEffect
val message = when (tripBookedResponse!!.data?.tripsBooked) {
null -> "Subscription error"
-1 -> "Trip cancelled"
else -> "Trip booked! 🚀"
}
// TODO use the message
}
subscription なのでFlowで変更を監視するのが一般的
TripsBookedSubscription
はgraphqlから作られたMutation実行用のクラス。
おわり
SQLDelightも設定ファイルからModelクラス作成までやってくれるので、マルチプラットフォーム環境だとこれがあたりまえなのか。
仕事柄モバイルに閉じこもってますが、外の世界もおもしろいですね。