2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Apollo Client Kotlin チュートリアルざっくりまとめ

Posted at

概要

本記事では、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()

LaunchListQuergraphqlから作られたQuery実行用のクラス。
Optional.present(cursor) はリクエスト時に設定するパラメータ。

レスポンス型 -> ApolloResponse<LaunchListQuery.Data>?

  • Mutationリクエスト
apolloClient.mutation(LoginMutation(email = email)).execute()

LoginMutationgraphqlから作られた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で変更を監視するのが一般的
TripsBookedSubscriptiongraphqlから作られたMutation実行用のクラス。

おわり

SQLDelightも設定ファイルからModelクラス作成までやってくれるので、マルチプラットフォーム環境だとこれがあたりまえなのか。
仕事柄モバイルに閉じこもってますが、外の世界もおもしろいですね。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?