🤔

【Flutter】【GraphQL】FlutterでGraphQL使ってみた話し②(API呼び出し編)

2024/09/06に公開

はじめに

本記事では、FlutterでGraphQLを利用を検討している,もしくわGraphQLで実装しなければならないがどこから手をつければ良いかわからないと言った方向けの記事になります。
第1弾フォルダ構成編を公開しているためこちらも併せて参照をお勧めします
https://zenn.dev/yanasan/articles/433d20cff16f2f

バージョン

  • flutter: 3.19.5
  • Dart:3.3.3

導入パッケージ

主に利用したパッケージです
https://pub.dev/packages/graphql_codegen
https://pub.dev/packages/graphql_flutter

graphql_flutter: ^5.1.2
graphql_codegen: ^0.14.0

本題

今回からどのようにAPIを定義してフロント側で取得するのかという部分に移っていきます。
実装する以前に調べていたらQuery$GetUser$WidgetのようにしてWidgetへ直接表示させるようなドキュメントが多かった印象です...
(FutureBuilderやfirebase_ui_firestoreで実装しているようなイメージ?)
今回はAPIとして呼び出せるようにしてみました。

GraphQLProvider

すみません、この辺り知識不足です...
詳しくはパッケージを見たらわかると思います。
私の認識としては「GraphQLClientを活用するなら追加をしよう!」という認識です。
ということで追加しましょう

私の場合はMyAppの部分へ追加しました

final client = GraphQLConfig().clientToQuery(accessToken: accessToken);
return GraphQLProvider(
      client: client,
      child: ScreenUtilInit(builder: (_, child) {
        return MaterialApp.router(
          supportedLocales: const [
            Locale('ja'),
          ],
          localizationsDelegates: const [
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          locale: const Locale('ja'),
          routerConfig: router,
          builder: EasyLoading.init(),
          debugShowCheckedModeBanner: false,
        );
      }),
    );

GraphQLConfigファイルの作成

GraphQLConfigはgraphql_flutterのパッケージでも同じ名前が存在するので違うネーミングがあれば教えてください....
とりあえずはGraphQLConfigで実装をしましょう
行なっているのは先ほどのclientを作成しているだけです。

class GraphQLConfig {
    ValueNotifier<GraphQLClient> clientToQuery({
        required accessToken,
      }) {
          Map<String, String> defaultHeaders = {
            'Authorization': 'Bearer $accessToken',
          };
          HttpLink httpLink = HttpLink(
            defaultHeaders: defaultHeaders,
            apiURL,
          );
          return ValueNotifier<GraphQLClient>(
            GraphQLClient(
              link: httpLink,
              cache: GraphQLCache(store: InMemoryStore()),
            ),
    );
}   

※この辺りのheader部分はプロジェクト毎で異なってくるかと思いますのでバックエンドのエンジニアとの連携で実装されてください

Map<String, String> defaultHeaders = {
            'Authorization': 'Bearer $accessToken',
          };
          HttpLink httpLink = HttpLink(
            defaultHeaders: defaultHeaders,
            apiURL,
          );

呼び出し方

これまでの準備してきた内容を掛け合わせて一般的?な呼び出しを行います
今回はAPIを叩いてResponseを取得しています
一例を見ながらいきましょう

Future<ResponseUserListVO> getUsers({
    Variables$Query$listUsers? variables,
    required String accessToken,
  }) async {
    final client = GraphQLConfig().clientToQuery(
      accessToken: accessToken
    );
    final options = Options$Query$listUsers(
      variables:
          variables?.page != null ? variables : variables?.copyWith(page: 1),
    );
    final result = await client.value.query$listUsers(options);
    if (result.hasException) {
      throw result.exception!.graphqlErrors.first.message;
    }
    final data = result.data;
    final responseUserList = ResponseUserListVO.fromJson(data?['listUsers']);
    return responseUserList;
  }

レスポンスの型

これはバックエンドエンジニアがどのように定義するかによるかと思いますので臨機応変に対応しましょう!
私の場合はResponse〇〇という形にしています

値の取得方法

先ほど作成したclientを実行=APIの通信?というイメージかなと思います
第1弾で説明したSchema.graphqlに値を追加していた場合にquery$listUsersのようなものが利用できます。

optionsは必須ではないです!
optionsはfilter、orderBy、limit、pageなどを定義するときに必要となります。
そしてvariablesがfilterなどに当たりますので追加している方は定義しましょう!

 final options = Options$Query$listUsers(
      variables:
          variables?.page != null ? variables : variables?.copyWith(page: 1),
);
final result = await client.value.query$listUsers(options);

エラーハンドリング

エラーになった際にGraphQLのエラーログだけ抜き出したいのでこのようにしています。
私はバックエンドエンジニアの方にエラーログ管理していただいてます。
そのため、APIが失敗した場合はこのログがtry{}catch(e){}で表示されるという簡単設計です

if (result.hasException) {
      throw result.exception!.graphqlErrors.first.message;
}

使ってみよう!

私の場合は状態管理する用のフォルダで呼び出して活用しています。

 final responseUserList = await GraphQLClient().getUsers(
        accessToken: accessToken,
        variables: Variables$Query$listUsers(
          filter: Input$TableUserFilterInput(
            is_active: Input$TableBooleanFilterInput(eq: true),
          ),
        ));

まとめ

「使ってみよう!」を見てもわかるようにfilterなどをかけるとかなりコードの量が膨らむなという印象です...
もちろん、valiablesを変数として持たせなくてもOKです。(あらかじめ指定しておく)
より良い実装の提案としては、variablesの箇所だけでもさらに別管理などするとコードとしてはかなりスッキリするかと思います。
まぁ、その分管理も大変になりますが...

Discussion