😀

【Flutter】【GraphQL】FlutterでGraphQL使ってみた話し①(フォルダ構成編)

2024/08/27に公開

はじめに

本記事では、FlutterでGraphQLを利用を検討している,もしくわGraphQLで実装しなければならないがどこから手をつければ良いかわからないと言った方向けの記事になります。

バージョン

  • 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

本題

先にフォルダ構成を展開してから軽〜く解説してきますね
上から順に役割とデモコードを利用して解説をしていきます

パッケージ利用方法

おっと忘れていました
フォルダ構成の前にパッケージの利用方法も共有しておきますね
基本的に記載するコードはgraphqlとなります。記載したgraphqlをコマンドを活用してdartへと変換することができるのがgraphql_codegenとなるのです!
graphqlのコードを書いた際は下記のコードを叩いてください

--delete-conflicting-outputsは生成されるファイルが既存のファイルと競合する場合に、それらの競合するファイルを削除するオプションになるため追加しなくてもOKです
詳しくはパッケージを参照してください

flutter pub run build_runner build --delete-conflicting-outputs

フォルダ構成

私はModels毎に作成しています

lib/
└── graphql/
    ├── fragments/
    │   └── user_fragment/ 
    │       ├── user_fragment.graphql
    │       └── user_fragment.graphql.dart //graphql_codegenの自動生成
    ├── inputs/
    ├── mutations/
    ├── queries/
    ├── types/
    ├── utils/
    └── schema.graphql 

fragments

複数のqueryやmutaionで再利用できる一部のフィールドセットを定義するためのものです。
→利用することで定義する回数を大幅に減らすことができました!
※Typeを定義していないとエラーになるので注意

GraphQLの良い点はフロント側が指定した値を抜き出し利用できるという点です。
そのため、機能or画面などでFragmentを作成してあげるのも良いと思います!

fragment UserFragment on User {
    id
    name
    email
    region { 
        ...RegionFragment
    }
}

Fragment内でTypeを指定する場合こちらのようになります
型の指定はここではしません、型の定義はTypeで行います

inputs

queryやmutationに渡す引数として使用するオブジェクトの型を定義するものです。

input CreateUserInput {
  id: String!
  bio: String
  name: String!
  email: String!
  iconUrl: String
}

types

schema内でオブジェクトの構造を定義するものです。

こちらで使用している「!」は必須の値という意味合いとなります。
※typeはdartのコードが生成されません

type User {
    id: String!
    bio: String
    name: String!
    email: String!
    iconUrl: String
    age: Int
    createAt:DateTime
}

Mutation

サーバー側のデータを作成、更新、削除する操作を定義するものです。
RESTAPIで言うPOST/DELETE/PUTに当たります。
ここもGraphqlの特徴の一つだと思っています

ここでようやくFragmentが活用されます!
本来はjson形式でレスポンスで返ってくる値を記載するのですが今回の場合はFragmentを活用しデータを取得しています。
inputもこちらで利用されていますね!

mutation createUser($input: CreateUserInput!) {
  createUser(input: $input) {
    ...UserFragment
  }
}

idだけなど限定的な値が欲しい場合下記のように記載することで実装可能です

createUser(input: $input) {
    id
  }

Queries

クライアントがサーバーからデータを取得するためのリクエストを定義するものです。
RESTAPIで言うGETに当たります

filter:取得データの種類を制限
limit:取得データ数の制限
orderBy:取得順のソート
page:ページネーションなどの追加取得機能のため

こちらの実装は取得する値やPJによって変わってくると思いますのでバックエンドエンジニアの方と連携しながら確認した方が効率的に進めることができると思います!

query listUsers//ここは自分がわかりやすい関数名でOK(
  $filter: TableUserFilterInput
  $limit: Int
  $orderBy: [OrderByUserInput]
  $page: Int
) {
  listUsers//こちら側はバックエンド側の実装に合わせる必要あり (
    filter:$filter 
    limit:$limit 
    orderBy:$orderBy 
    page:$page
  ) {
    items {
        ...UserFragment
    }
  }
}

Utils

共通処理やユーティリティ関数を定義するもので、複数のQueryやMutationで利用される共通ロジックを整理・再利用するために使われます

enum UploadTarget {
  USER
  ADMIN
}

schema.graphql

データの構造や操作を定義するスキーマを記述するファイルです。このファイルには、QueryやMutation、タイプ定義などが含まれ、APIの全体像を示すものです。
※こちらに関数名がバックエンド側と同じものが記載されていなかった場合はエラーとなりますので注意

scalar DateTime
type Query {
  # User
  getUser(id:String!): User
  getMyUser:User
  listUsers(
    filter: TableBattleFilterInput!,
    limit: Int!,
    orderBy: [OrderByBattleInput]!,
    page: Int!
  ): UserConnection
}
type Mutation {
  # user
  createUser(input: CreateUserInput!): User
  updateUser(input: UpdateUserInput!, condition: TableUserConditionInput): User
  deleteUser(input: DeleteUserInput!, condition: TableUserConditionInput): User
}

ここで注意すべき箇所はscalarです
こちらの定義方法はpubspec.yamlと同じ階層にbuild.yamlを作成し定義していきます
こちらはgraphql_codegenのパッケージにも記載されているため確認をお勧めします

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          scalars:
            DateTime:
              type: DateTime
          scopes:
            - lib/graphql/**
          clients:
            - graphql
            - graphql_flutter

まとめ

ざっくりと解説してきましたがGraphqlの定義について理解できたでしょうか?
正直なところ、バックエンドの方も同じものを記載するのでコピーで良いです笑
個人的につまづいた箇所としては
typeとtypeが入れ子になっている状態の定義手法です。(Fragmentの箇所)

少しでも異なっていたらエラーでコードが生成されないので注意深くチェックしていきましょう!

Discussion