😇

Ferry Graphql

2025/02/02に公開

公式の解説が親切ではない

FlutterでGraphQLを使うときに、ferryと呼ばれるライブラリを使うことがあるのだが、これは難しいと思った😅

https://ferrygraphql.com/

だって公式に書いてあることわからないんだもん😇
ポケモンのAPIからデータをとってくるようだ???

https://pokeapi.co/docs/graphql#google_vignette

実行すると長いログが表示される。

# This curl retrieves all items and relative costs
curl 'https://beta.pokeapi.co/graphql/v1beta' \
-H 'content-type: application/json' \
-H 'accept: */*' \
--compressed \
--data-binary @- << EOF
{
    "query": "query getItems{pokemon_v2_item{name,cost}}",
    "variables": null,
    "operationName":"getItems"
}
EOF

完成品があるのでご興味ある方は試してみてください。

Flutter + Ferry GraphQL セットアップガイド

環境構築手順

  1. プロジェクトの作成
flutter create ferry_demo
cd ferry_demo
  1. 必要なパッケージの追加
flutter pub add \
  ferry \
  ferry_flutter \
  gql_http_link \
  built_collection \
  built_value

flutter pub add --dev \
  build_runner \
  ferry_generator \
  built_value_generator
  1. ファイル構造
lib/
  ├── graphql/
  │   ├── schema.graphql      # GraphQLスキーマ
  │   └── queries/
  │       └── get_items.graphql  # クエリファイル
  └── main.dart
  1. build.yamlの設定
targets:
  $default:
    builders:
      ferry_generator|graphql_builder:
        enabled: true
        options:
          schema: ferry_demo|lib/graphql/schema.graphql
      ferry_generator|serializer_builder:
        enabled: true
        options:
          schema: ferry_demo|lib/graphql/schema.graphql

GraphQLの実装例

  1. スキーマ定義 (lib/graphql/schema.graphql)
type Query {
  pokemon_v2_item: [Item!]!
}

type Item {
  name: String!
  cost: Int!
}
  1. クエリの作成 (lib/graphql/queries/get_items.graphql)
query GetItems {
  pokemon_v2_item {
    name
    cost
  }
}
  1. コード生成
dart run build_runner build

使用例 (lib/main.dart)

Client initClient() {
  final httpLink = HttpLink('https://beta.pokeapi.co/graphql/v1beta');
  final client = Client(link: httpLink);
  return client;
}

// データの取得
Operation(
  client: client,
  operationRequest: GGetItemsReq((b) => b),
  builder: (context, response, error) {
    if (response == null || response.loading) {
      return const CircularProgressIndicator();
    }
    final items = response.data?.pokemon_v2_item;
    // データの表示
  },
)

Ferryの特徴

  • 型安全なGraphQLクライアント
  • コード生成による型定義の自動生成
  • IDEでのコード補完サポート
  • Streamベースのリアクティブな実装

注意点

  • スキーマファイルは正確に定義する必要がある
  • コード生成は変更のたびに必要
  • エンドポイントのURLは正確に指定する

このセットアップで、PokeAPIのGraphQLエンドポイントからデータを取得し、型安全な方法で表示することができます。

example

main.dartの全体のコードです。

main.dart
import 'package:ferry/ferry.dart';
import 'package:ferry_demo/graphql/queries/__generated__/get_items.req.gql.dart';
import 'package:ferry_flutter/ferry_flutter.dart';
import 'package:flutter/material.dart';
import 'package:gql_http_link/gql_http_link.dart';

void main() {
  final client = initClient();
  runApp(MyApp(client: client));
}

Client initClient() {
  final httpLink = HttpLink('https://beta.pokeapi.co/graphql/v1beta');
  final client = Client(link: httpLink);
  return client;
}

class MyApp extends StatelessWidget {
  final Client client;
  const MyApp({super.key, required this.client});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(useMaterial3: true),
      home: HomePage(client: client),
    );
  }
}

class HomePage extends StatelessWidget {
  final Client client;
  const HomePage({super.key, required this.client});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          backgroundColor: Colors.yellow, title: const Text('Pokemon Items')),
      body: Operation(
        client: client,
        operationRequest: GGetItemsReq((b) => b),
        builder: (context, response, error) {
          if (response == null || response.loading) {
            return const Center(child: CircularProgressIndicator());
          }

          if (response.hasErrors) {
            return Center(
                child: Text(response.graphqlErrors?.toString() ?? 'Error'));
          }

          final items = response.data?.pokemon_v2_item;
          if (items == null) return const Center(child: Text('No items'));

          return ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              final item = items[index];
              return ListTile(
                title: Text(item.name),
                subtitle: Text('Cost: ${item.cost}'),
              );
            },
          );
        },
      ),
    );
  }
}

こんな感じで表示されました。これを使ってる人たちは仕事大変そーって思いました😅
REST APIの方が楽ですね。

Discussion