Flutter × GraphQL

宣言的モバイルアプリ開発

2025/2/7

吉田 航己

top

吉田 航己

Yoshida Koki


株式会社サイバーエージェント
モバイルアプリエンジニア

X : @koki8442

GitHub : lllttt06

目次

1. GraphQL の概要

2. Flutter での GraphQL 使用例

3. Flutter で GraphQL を使う利点

4. おわりに

1. GraphQL の概要

GraphQL とは?

REST の問題を解決するための API クエリ言語およびランタイム

  • データを Node と Edge のグラフで構造化し、必要に応じてそのグラフの一部を利用する
  • 単一のエンドポイントから必要なデータだけを Query するため、オーバー(アンダー)フェッチを防げる
2. Flutter での GraphQL 使用例

使用パッケージ

GraphQL クライアント:graphql_flutter (graphql)

GraphQL 自動生成関連:graphql_codegen

2. Flutter での GraphQL 使用例

query の定義と自動生成ファイル

query を .graphql に記述し、.dart のコードを自動生成する

# setting_screen_query.graphql
query Setting {
  me {
    id
  }
}
// setting_screen_query.graphql.dart
class Query$Setting {
  Query$Setting({
    required this.me,
    this.$__typename = 'Query',
  });

  factory Query$Setting.fromJson(Map<String, dynamic> json) {
    final l$me = json['me'];
    final l$$__typename = json['__typename'];
    return Query$Setting(
      me: Query$Setting$me.fromJson((l$me as Map<String, dynamic>)),
      $__typename: (l$$__typename as String),
    );
  }

  final Query$Setting$me me;

  final String $__typename;

  Map<String, dynamic> toJson() {
    final _resultData = <String, dynamic>{};
    final l$me = me;
    _resultData['me'] = l$me.toJson();
    final l$$__typename = $__typename;
    _resultData['__typename'] = l$$__typename;
    return _resultData;
  }

  @override
  int get hashCode {
    // ...
  }

  @override
  bool operator ==(Object other) {
    // ...
  }
}

class Query$Setting$me {
  Query$Setting$me({
    required this.id,
    this.$__typename = 'User',
  });

  factory Query$Setting$me.fromJson(Map<String, dynamic> json) {
    final l$id = json['id'];
    final l$$__typename = json['__typename'];
    return Query$Setting$me(
      id: (l$id as String),
      $__typename: (l$$__typename as String),
    );
  }

  final String id;

  final String $__typename;

  Map<String, dynamic> toJson() {
    final _resultData = <String, dynamic>{};
    final l$id = id;
    _resultData['id'] = l$id;
    final l$$__typename = $__typename;
    _resultData['__typename'] = l$$__typename;
    return _resultData;
  }

  @override
  int get hashCode {
    // ...
  }

  @override
  bool operator ==(Object other) {
    // ...
  }
}

// ...

graphql_flutter.QueryHookResult<Query$Setting> useQuery$Setting(
        [Options$Query$Setting? options]) =>
    graphql_flutter.useQuery(options ?? Options$Query$Setting());

// ...
2. Flutter での GraphQL 使用例

SettingScreen の例

データの取得を Screen 内の useQuery$Setting で行う

// setting_screen.dart
class SettingScreen extends HookConsumerWidget {
  const SettingScreen({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final isGuestUser = useIsGuestUser();
    final query = useQuery$Setting(options);
    return Scaffold(
      // ...
      body: GraphQLQueryContainer(
        query: query,
        onLoadingWidget: SkeletonSettingScreen(isGuestUser: isGuestUser),
        onErrorWidget: (error, stackTrace) => ErrorContainer(
          error: error,
          stackTrace: stackTrace,
          onAction: query.refetch,
        ),
        child: (data) {
          return SingleChildScrollView(
            // ...
          );
        },
      ),
    );
  }
}
3. Flutter × GraphQL の利点

Flutter × GraphQL の利点

1. スキーマファースト開発

2. クライアントキャッシュ

3. 宣言的 UI との親和性

3. Flutter × GraphQL の利点

スキーマファースト開発

最初に GraphQL Schema を定義し、Schema 定義に合うようにコード(API など)を実装する手法

  • クライアントーサーバー間のコミュニケーションコスト削減
  • API 実装完了前でも Schema からデータをモック可能
  • file-sync-action でサーバーでマージした Schema を同期
3. Flutter × GraphQL の利点

スキーマファースト開発

3. Flutter × GraphQL の利点

クライアントキャッシュ

モバイルアプリ観点で個人的に GraphQL を採用する一番のメリット

キャッシュの更新、保存を自動で GraphQL クライアントが行い、useQuery を使用している Widget は別の Query や Mutation でのキャッシュの更新に伴って UI も自動で更新される


declarative diagram
3. Flutter × GraphQL の利点

クライアントキャッシュの仕組み

GraphQL クライアントでは、Query, Mutation などの Operation の結果を正規化してキャッシュ。正規化は以下の 3 ステップで行われる。

  1. Operation 結果を分割して個別のオブジェクトにする
  2. 分割したオブジェクトに一意の key をつける
  3. それぞれのオブジェクトをフラットに保存する
3. Flutter × GraphQL の利点

クライアントキャッシュの仕組み

# リクエスト
query {
  comics {
    id
    title
    episodes {
      id
      title
  }
}
# レスポンス
[   
  Comics: [
    {
      id: 1,
      __typename: “Commics”,
      title: “WebToon Title”,
      episodes: [
        {
          id: 1,
          __typename: “Episodes”,
          title: “Revenge”,
        },
        {
          id: 2,
          __typename: “Episodes”,
          title: “Imagination”,
        },
      ],
    },     
  ]
]
3. Flutter × GraphQL の利点
3. Flutter × GraphQL の利点

宣言的 UI との親和性

データを元に UI を構築する宣言的 UI は、
GraphQL クライアントのキャッシュ機構との親和性が高い。

ある Operation で正規化されたキャッシュデータが更新されると、
それを参照しているすべての UI が更新されるため。

REST では同じことは出来ないの??

3. Flutter × GraphQL の利点

宣言的 UI との親和性

  • Redux の公式ドキュメント Store データの正規化
    → 正規化を手動で行う必要がある。GraphQL はデータの構造が Schema に Graph として表現されているため機械的にこれができる。

  • React の TanStack QuerySWR
    → 基本的に URL を key としてキャッシュするため、個々のデータに分割してキャッシュされない。
    → Flutter にはこれらにインスパイアされた fQuery がある。

3. Flutter × GraphQL の利点

おわりに

詳細はこちら

参考文献

この GraphQL クライアント技術の利点は REST にも応用可能。

実際に Redux の公式ドキュメントでも [Store データの正規化](https://redux.js.org/usage/structuring-reducers/normalizing-state-shape)について言及されている。