FlutterでFerryを使ってGraphQL APIにアクセスする方法
この記事について
FlutterのGraphQLクライアントであるFerryを使ってGraphQL APIにアクセスする方法について紹介します。内容についてはFerry公式ドキュメント+Ferry GitHubページの公式サンプルをベースに簡略化しています。
コーディングの準備
ターミナルで下記のコマンドを実行してFlutterプロジェクトを作成します。VSCodeを使っている場合はCommand + Shift + P
を押してからFlutter: New Projectを実行してもOKです。
flutter create hello_ferry
続いてターミナルで下記のコマンドを実行してディレクトリとファイルを作成します。
cd hello_ferry
mkdir -p lib/src/components/graphql
touch build.yaml
touch lib/src/components/pokemon_card.dart
touch lib/src/components/pokemon_list.dart
touch lib/src/components/graphql/all_pokemon.graphql
touch lib/src/components/graphql/pokemon_card_fragment.graphql
続いてターミナルで下記のコマンドを実行して必要なパッケージをインストールします。
flutter pub add ferry ferry_flutter gql_http_link built_collection
flutter pub add --dev ferry_generator build_runner
flutter pub get
GraphQLスキーマのダウンロード
ターミナルで下記のコマンドを実行してget-graphql-schemaをインストールします。
npm install -g get-graphql-schema
続いてターミナルで下記のコマンドを実行してGraphQLスキーマをダウンロードします。
get-graphql-schema https://pokeapi.dev > lib/schema.graphql
ダウンロードが成功するとlib/schema.graphqlに下記のファイルが作成されます。
directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE
type Ability {
id: String
name: String
slot: Int
is_hidden: Boolean
is_main_series: Boolean
pokemons: [Int]
}
# 以下、省略
GraphQLクラスの自動生成
エディタでlib/src/components/graphql/all_pokemon.graphqlとlib/src/components/graphql/pokemon_card_fragment.graphqlを開いて下記のGraphQLクエリの内容を入力します。
続いてエディタでbuild.yamlを開いて下記の内容を入力します。パッケージ名がhello_ferry
ではない場合やスキーマファイルのパスがlib/schema.graphql
ではない場合は適宜編集します。
ターミナルで下記のコマンドを実行してGraphQLクラスを自動生成します。
flutter pub run build_runner build
自動生成が成功するとターミナルに下記のメッセージが表示されます。
[INFO] Succeeded after 13.8s with 27 outputs (40 actions)
自動生成されるファイルは下記の通りです。
- lib/generated/schema.schema.gql.dart
- lib/generated/schema.ast.gql.dart
- lib/generated/schema.schema.gql.g.dart
- lib/generated/serializers.gql.dart
- lib/generated/serializers.gql.g.dart
- lib/src/components/graphql/generated/all_pokemon.ast.gql.dart
- lib/src/components/graphql/generated/all_pokemon.data.gql.dart
- lib/src/components/graphql/generated/all_pokemon.data.gql.g.dart
- lib/src/components/graphql/generated/all_pokemon.req.gql.dart
- lib/src/components/graphql/generated/all_pokemon.req.gql.g.dart
- lib/src/components/graphql/generated/all_pokemon.var.gql.dart
- lib/src/components/graphql/generated/all_pokemon.var.gql.g.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.ast.gql.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.data.gql.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.data.gql.g.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.req.gql.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.req.gql.g.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.var.gql.dart
- lib/src/components/graphql/generated/pokemon_card_fragment.var.gql.g.dart
パーミッションの設定
macOSの場合はmacos/Runner/DebugProfile.entitlementsとmacos/Runner/Release.entitlementsに下記の内容を追加します。
<key>com.apple.security.network.client</key>
<true/>
ちなみにDebugProfile.entitlementsにはcom.apple.security.network.server
というよく似たキーがありますがこれを消すとFlutterアプリが起動しなくなるのでご注意ください(私は15分くらいハマりました)。
AndroidとiOSについて未検証ですがFlutter公式ドキュメントの下記のページが参考になりそうです。
コーディング
エディタで下記のファイルを開いて内容を入力します。
lib/main.dart
lib/src/components/pokemon_list.dart
lib/src/components/pokemon_card.dart
動作確認
ターミナルで下記のコマンドを実行してFlutterアプリを起動します。VSCodeを使っている場合はF5
を押すかDebug: Start Debuggingを実行してもOKです。
flutter run
Flutterアプリが起動してポケモンの名前一覧が表示されることを確認します。
おわりに
Ferryの公式ドキュメントは簡潔で読みやすくて良いのですが説明が若干不足しているように感じる部分が多く、入門するにあたりハマってしまうことが多かったです。最後になりますが私が時間を浪費してしまったポイント等について紹介したいと思います。
インストールするパッケージ
Setupのページでは下記のパッケージをインストールするように説明されています。
dependencies:
ferry: #[latest-version]
gql_http_link: #[latest-version]
dev_dependencies:
ferry_generator: #[latest-version]
build_runner: #[latest-version]
上記に加えてdependenciesとして下記2つのパッケージをインストールするのがおすすめです。
- ferry_flutter: FlutterでFerryを使うのを便利にするパッケージ
- built_collection: VSCodeでBuiltListの自動インポートができるようになる
GrqphQLエンドポイントURL
Generate GraphQL Classesのページでは下記のGrqphQLクエリが記載されています。
query AllPokemon($first: Int!) {
pokemons(first: $first) {
id
name
maxHP
image
}
}
上記のクエリは恐らく https://graphql-pokemon2.vercel.app を対象にしているものと思われます。一方、公式サンプルでは https://pokeapi.dev を対象にしています。結論としてはどちらを使っても良いのですが入門時はどちらを使えば良いか悩みました。
https://graphql-pokemon2.vercel.app を使うメリット/デメリットは下記の通りです。
- メリット:上記のクエリがそのまま使える、ポケモンの画像を表示できる(後述)
- デメリット:ソースコードの大幅な改変が必要
https://pokeapi.dev を使うメリット/デメリットは下記の通りです。
- メリット:ソースコードの改変がほぼ不要
- デメリット:上記のクエリがそのまま使えない(代わりに公式サンプルのクエリが使えば良い)、ポケモンの画像を表示できない(後述)
動作確認の時にポケモンの画像を表示できないのは残念ですが https://pokeapi.dev を使った方が簡単です。ちなみに https://graphql-pokemon2.vercel.app を使うとGraphQLクラスを自動生成する時にschema.schema.gql.dartなどの一部のファイルが出力され無くなります、理由は不明です。入門するにあたりこのファイルは必要ないのですが自分のやり方が間違っているのではないかという不安に駆られて色々と調べるのに時間を費やしてしまいました。
クライアントの初期化
Setupのページではクライアントの初期化の手順が下記のように紹介されています。
import 'package:gql_http_link/gql_http_link.dart';
import 'package:ferry/ferry.dart';
import 'package:<your_pkg>/<path_to_your_schema>__generated__/schema.schema.gql.dart' show possibleTypesMap;
final link = HttpLink("[path/to/endpoint]");
final cache = Cache(possibleTypes: possibleTypesMap);
final client = Client(link: link, cache: cache);
possibleTypesMap
が{}
である場合、コンパイルが通らないので下記の修正する必要があります。
final cache = Cache();
Configuring the cacheのページではpossibleTypes
について下記のように説明されています。
When the schema contains unions, interfaces queries use fragments, the cache needs a little help to correctly normalize/denormalize data. In this case, pass the possibleTypes parameter to the cache constructor
上記の英文はよくわからないのですが恐らくunion、interface、フラグメントを使うクエリを使っている場合はCache
のコンストラクターにpossibleTypes
を指定してください、といった意味なのではないかと思います。逆にこれらを使っていない場合はpossibleTypes
の指定が不要なのではないかと思います。
ネットワークのパーミッション
パーミッションの設定で紹介したようにmacOSやAndroidではHTTPリクエストを送信するのに設定が必要になります。多分iOSも必要なのではないかと思います。iOSやAndroidのアプリ開発の知識がある人にとっては当然のことなので割愛されたのかも知れませんがSetupのページに1行でも良いので「Ferryを利用するには適切なパーミッション設定が必要です」と書いておいてくれれば良いのにと思います。
ポケモンの画像が表示されない
GraphQLエンドポイントURLとして https://pokeapi.dev を使うとポケモン画像のURL(Pokemonタイプのavatarフィールド)として https://pokeres.bastionbot.org/images/pokemon/1.png のような結果が得られますが、このURLにアクセスしてもポケモンの画像は表示されません。Google Chromeなどのブラウザからアクセスしても表示されないので恐らくサーバー側の問題だと思うのですが、自分の方が何かを間違えているのではないかという不安に駆られて色々と調べるのに時間を費やしてしまいました。
Discussion