🦋

Flutter + artemis で型が効いてる GraphQL の useQuery hooks を作る

2021/12/20に公開

こちらはGraphQL アドベントカレンダー20日目の記事です。

React 界隈で GraphQL を使っている方であればお馴染みな useQuery と useMutation

例えば Apollo では次のような使い心地です。

function Hello() {
  const { loading, error, data } = useQuery(GET_GREETING, {
    variables: { language: 'english' },
  });
  if (loading) return <p>Loading ...</p>;
  return <h1>Hello {data.greeting.message}!</h1>;
}

React の民である私が Flutter + GraphQL で実装を始めた時に真っ先に欲しいと思ったのはこのインターフェイスでした。

ただ、Flutter でデファクトっぽい GraphQL ライブラリの graphql_flutter にはそれがなさそうだったので、 useQuery 関数を自作してみました。

useQuery

こんな感じになりました。

QueryResult useQuery<DataType>(
    BuildContext context, DocumentNode query,
    [Object variables]) {
  final client = GraphQLProvider.of(context).value;
  final state = useState<QueryResult>(QueryResult());

  useEffect(() {
    final promise = client.query(
      QueryOptions(document: query, variables: variables),
    );
    promise.then((result) {
      state.value = result;
    });
    return () {};
  }, []);
  return state.value;
}

altemis と組み合わせ使う

ただ上記だけだと result.data にクエリ固有の型が効きません。
なので artemis というライブラリを組み合わせてみます。

altemis は GraphQL の .graphql ファイルによるクエリの定義からそのクエリをコードで実行な形の変数や型を生成してくれる便利ツールです。

https://pub.dev/packages/artemis

これから生成されるものを用いて型がついた状態で返せるように次のように作ります。

class ExampleQuery$QueryReturnType {
  bool isLoading;
  OperationException exception;
  ExampleQuery$Query data;

  ExampleQuery$QueryReturnType(this.isLoading, this.exception, this.data);
}

ExampleQuery$QueryReturnType useExampleQueryQuery<DataType>(BuildContext context) {
  final result = useQuery<ExampleQuery$Query>(context, EXAMPLE_QUERY_QUERY_DOCUMENT);
  return ExampleQuery$QueryReturnType(result.isLoading, result.exception, result.data == null ? null : ExampleQuery$Query.fromJson(result.data));
}

そしてこれを利用する Widget で次のように使います。

class HomePageWidget extends HookWidget {
  const HomePageWidget({Key key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final result = useExampleQueryQuery(context);

    if (result.isLoading) {
      return Text("Loading");
    }

    return Text(result.data.name); // 型が効いてる!
  }
}

という感じでいい感じですね。
ちなみに「この HomePageWidget から直接 useQuery 呼び出しちゃダメなの?」と思った方もいらっしゃるかもしれません。

別にそれで大丈夫なのですが、将来的に graphql-codegen でこの Query 毎の hooks を生成するプラグインでも作ろうかと画策しているので、それのための布石として上記のように分けてみました。
年末年始の暇な時間に開発しようと思っているので乞うご期待ください。

Flutter + GraphQL マジで文献がなく(英語で探しても…)もし同じ構成で作っている方がいらっしゃればどんどん情報発信して一緒に盛り上げましょう!💪

Discussion