株式会社Neverのkousoです。
株式会社Neverは「NEVER STOP CREATE 作りつづけること」をビジョンに掲げ、理想を実現するためにプロダクトを作り続ける組織です。モバイルアプリケーションの受託開発、技術支援、コンサルティングを行っております。アプリ開発のご依頼や開発面でのお困りの際はお気楽にお問合せください。
https://neverjp.com/contact/
概要
最近、graphQLを使用しているので、勉強のためにタイトル記載のパッケージを使用してみました。
色々なパッケージがある中、mutationやqueryをコード生成してくれるgraphql_codegen、キャッシュ機能があるgraphql_flutterが実際に活かせそうだと思ったので使用してみました。
GraphQLのAPI
今回はこちらを使用しました
デモアプリ
20件取得して、次のページでさらに20件取得してますgraphql_codegen
- graphql_codegen
- build_runner(コード生成に必要)
のパッケージをpubspec.yamlに追加します
schema.graphqlを追加
IDEのGraphiQLやApollo Studioもありますが、今回はPostmanを使用してスキーマを取得しました。
schema.graphqlファイルは、Dartのコード生成に必須です。
query、mutation、subscriptionなどの構造を定義して、そのスキーマに基づいて、queryやmutationを実装し、適切なデータ型でレスポンスできます。
つまり、このファイルに基づいて.graphqlで定義されたqueryなどを解釈してコード生成するので、ないとコード生成されないです。
私自身、少しはまりました。。。
type Query {
characters(page: Int!, filter: FilterCharacter): Characters
locations(page: Int!): Locations
episodes(page: Int!): Episodes
}
type Character {
id: ID!
name: String
image: String
}
type Location {
id: ID!
name: String
type: String
}
type Episode {
id: ID!
name: String
episode: String
}
type Characters {
results: [Character]
}
type Locations {
results: [Location]
}
type Episodes {
results: [Episode]
}
input FilterCharacter {
name: String
}
queries.graphqlを追加
データ取得に必要な情報を記載していきます
query GetCharacters($page: Int!, $name: String!) {
characters(page: $page, filter: { name: $name }) {
results {
id
name
image
}
}
}
query GetLocations {
locations(page: 1) {
results {
id
name
type
}
}
}
query GetEpisodes {
episodes(page: 1) {
results {
id
name
episode
}
}
}
自動生成の設定
graphql_codegenでコードを自動生成するので、その設定をします
build.yamlに下記を追加
targets:
$default:
builders:
graphql_codegen:
options:
scopes:
- lib/graphql/**
clients:
- graphql
- graphql_flutter
graphql_flutterを使用するので追加してます
lib/graphql/**
lib/graphql/ ←コード生成されず
コード生成されずにハマったので、**は必要です!!
build_runnerでコード生成
dart run build_runner build
上記コマンドでコード生成します
上記のように、ファイルが生成されます
比較するとわかりやすいので、codegenの有無
codegenを使用しない場合
const String getCharacters = r'''
query GetCharacters($page: Int!, $name: String!) {
characters(page: $page, filter: { name: $name }) {
results {
id
name
image
}
}
}
''';
const String getLocations = r'''
query {
locations(page: 1) {
results {
id
name
type
}
}
}
''';
const String getEpisodes = r'''
query {
episodes(page: 1) {
results {
id
name
episode
}
}
}
''';
このように、ハードコードで書いていくことになります
実際、タイプミスでエラーになって悩んだ経験もあるので、そういうリスクは減らしたいです。
codegenを使用する場合
query GetCharacters($page: Int!, $name: String!) {
characters(page: $page, filter: { name: $name }) {
results {
id
name
image
}
}
}
query GetLocations {
locations(page: 1) {
results {
id
name
type
}
}
}
query GetEpisodes {
episodes(page: 1) {
results {
id
name
episode
}
}
}
このように記載できます
コード整形もできますし、コード生成の段階でエラーがでます。
graphql_flutter
- graphql_codegen
- flutter_hooks
のパッケージをpubspec.yamlに追加します
状態管理にflutter_hooksを使用します
class MyApp extends HookWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final HttpLink httpLink = HttpLink('https://rickandmortyapi.com/graphql');
final client = useState<GraphQLClient>(
GraphQLClient(
link: httpLink,
cache: GraphQLCache(),
),
);
return GraphQLProvider(
client: client,
child: const MaterialApp(
home: CharacterList(),
),
);
}
}
キャッシュのところでは、Hiveを使用してローカルDBに保存するのもよさそうです
一覧表示
Query$GetCharacters$Widget(
options: Options$Query$GetCharacters(
variables: Variables$Query$GetCharacters(
page: index.value,
name: searchText.value,
),
),
builder: (
QueryResult result, {
VoidCallback? refetch,
FetchMore? fetchMore,
}) {
if (result.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (result.hasException) {
return Text(result.exception.toString());
}
final data = result.data;
if (data == null) {
return const Text('データがありません');
}
final charactersResponse =
CharactersResponse.fromJson(data['characters']);
final characters = charactersResponse.results;
return ListView.builder(
itemCount: characters.length,
itemBuilder: (context, index) {
final character = characters[index];
return ListTile(
title: Text(character.name ?? ''),
leading: Image.network(character.image ?? ''),
);
},
);
},
),
codegenで生成したコードは上記のように記載できます。
Query$GetCharacters$Widget
:codegenで生成されたウィジェットで、この場合はGetCharactersのqueryの結果を表示するWidgetです。
options: Options$Query$GetCharacters(
variables: Variables$Query$GetCharacters(
page: index.value,
name: searchText.value,
),
),
queryを実行するオプションを設定します。
Variables$Query$GetCharacters
:クエリに必要な変数を指定しています(この場合は、pageの数字と、キャラクターの名前です)。
mutationの場合は、mutaions.graphqlでmutationを生成して、QueryのところをMutationに変更します。
コード全文
class CharacterList extends HookWidget {
const CharacterList({super.key});
@override
Widget build(BuildContext context) {
final searchText = useState<String>('');
final index = useState<int>(1);
return Scaffold(
appBar: AppBar(
title: const Text('Rick and Morty Characters'),
),
body: Column(
children: [
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TextFormField(
decoration: const InputDecoration(
labelText: 'Search',
border: OutlineInputBorder(),
),
onChanged: (value) {
searchText.value = value;
},
),
),
),
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
if (index.value > 1) {
index.value--;
}
},
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () => index.value++,
),
],
),
Expanded(
child: Query$GetCharacters$Widget(
options: Options$Query$GetCharacters(
variables: Variables$Query$GetCharacters(
page: index.value,
name: searchText.value,
),
),
builder: (
QueryResult result, {
VoidCallback? refetch,
FetchMore? fetchMore,
}) {
if (result.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (result.hasException) {
return Text(result.exception.toString());
}
final data = result.data;
if (data == null) {
return const Text('データがありません');
}
final charactersResponse =
CharactersResponse.fromJson(data['characters']);
final characters = charactersResponse.results;
return ListView.builder(
itemCount: characters.length,
itemBuilder: (context, index) {
final character = characters[index];
return ListTile(
title: Text(character.name ?? ''),
leading: Image.network(character.image ?? ''),
);
},
);
},
),
),
],
),
);
}
}
終わりに
graphql_codegen、graphql_flutterともに、いいパッケージだと思いました。
query、mutationはgraphql_codegenで自動生成し、Widget内で状態が完結する場合は、hooksとgraphql_flutter、他の画面でも状態管理が必要な場合はRiverpodを使用するなど、自分の中でそれぞれの役割が明確になった気がします。
他にもgraphQLのパッケージがあるので、使用、比較してみたいと思いました。
ありがとうございました!