让我们尝试使用Flutter和GraphQL

首先

你使用过GraphQL吗?
在我个人制作服务时,我喜欢使用GraphQL。我特别喜欢使用GraphQL的两个好处。

    • レスポンスのデータ型がはっきりと分かる

 

    呼び出し側でデータ構造のどこまで取得するか決められる

我经常看到使用TypeScript等语言编写的GraphQL示例代码,但是很少看到使用Dart编写的GraphQL代码,因此我希望能够介绍一些示例代码来传达GraphQL的优点。

如果你觉得很不错,我在文章中准备了示例代码,希望你能试着运行一下。

简述

因为有人提供了一个可以获取到 列表的 GraphQL 服务器,
所以我使用它来在 Flutter 上创建了一个显示 列表的示例代码。
通过观察上述代码的创建过程,我将介绍如何在 Flutter 中使用 GraphQL。

 

スクリーンショット 2021-12-13 0.14.32.png

 

这个项目的目标人群是什么样的人?

    • GraphQLはよく聞くけどまだ使ったことはない人

 

    • Flutter以外ではGraphQLを使ったことがあるけどFlutterでの使い方がわからない人

 

    FlutterでGraphQLを使ってみたが、型がはっきりしているなどの長所をうまく活かせなかった人

尝试使用GraphQL体验一下

スクリーンショット 2021-12-13 0.31.32.png

当你打开页面时,大致可见

    • クエリを書くところ

 

    • 実行結果

 

    各データの型等の説明

在GraphQL服务器中,提供这样的播放环境是非常令人高兴的。

因为被要求在说明中传递参数first(每次想要获取的数据数量),所以我写了以下查询,成功获取到了10个宝可梦的信息。
现在我们将根据这个查询准备从Flutter调用GraphQL。

{
  pokemons(
    first: 10
  ){
    name
    image
    types
  }
}

在Flutter中安装GraphQL库

这次我们将使用名为artemis的包来创建样例代码,原因是我们之前有使用过它。

由于这个包存在缓存不起作用等缺点,因此在选择在自己的项目中采用时,我们建议考虑使用ferry等其他的包。

阿尔忒弥斯主要由以下两个部分组成。

    • 使用するGraphQLサーバーのSDL(型定義などが書かれたファイル)と自分が叩きたいクエリを読み込んでいい感じのDartコードを自動生成してくれる機能

 

    上記で自動生成したコードを使用してGraphQLサーバーを叩いてくれるClient機能

因此,需要将与自动生成相关的包和与客户端相关的包放入其中。
然后,在pubspec.yaml中添加它们,并运行flutter pub get。
(在2021/12/12,flutter版本2.5.3中正常工作。)

# dependenciesにはClient機能に関するパッケージを入れてます
dependencies:
  artemis: ^7.2.6-beta
  equatable: ^2.0.3
  gql: ^0.13.1-alpha
  # 2021/12/12にまっさらな状態でpub getするとこのパッケージの別バージョンでエラーが出たので一旦このバージョンで固定
  gql_exec: ^0.3.1-alpha+1635149947799
  json_annotation: ^4.3.0
# dev_dependenciesにはコード自動生成機能に関するパッケージを入れてます
dev_dependencies:
  build_runner: ^2.1.4
  json_serializable: ^6.0.1

尝试使用Flutter来使用GraphQL

首先,我们从GraphQL服务器的SDL和想要执行的查询生成Dart代码。
接下来,我们使用该代码在Flutter中编写用于在屏幕上显示信息的代码。

代码的自动生成

スクリーンショット 2021-12-13 0.56.04.png
    1. 在URL输入框中输入目标GraphQL服务器的URL

 

    1. 点击Docs

 

    1. 点击三个点

 

    选择导出SDL

可以下载。将该文件放置在项目的根目录中。

2. 创建一个包含所需查询的文件。

为了方便处理,本次将文件分成以下两部分进行自动代码生成。

    ポケモンの情報のどのフィールドを使用するか宣言する部分(fragment)
fragment pokemonField on Pokemon {
  id
  name
  image
  types
}
    クエリ部分
query searchPokemons {
  pokemons(
      first: 20
  ) {
    ...pokemonField
  }
}

3. 创建与自动化生成相关的设置文件
在项目的根目录下放置以下文件。

targets:
  $default:
    sources:
      - lib/**
      - schema.graphql
    builders:
      artemis:
        options:
          fragments_glob: lib/gql/fragment.graphql
          schema_mapping:
            - schema: schema.graphql
              queries_glob: lib/gql/queries/*.graphql
              output: lib/gql/generated/generated.dart

在fragments_glob中指定用于fragments的字段定义文件的位置,
在queries_glob中指定查询文件的位置,
在output中指定放置自动生成的代码的位置。

当使用以下命令时,将自动生成代码到lib/gql/generated/generated.dart,其内容与上述配置文件一致。

# fvmを使用している方は最初にfvmをつける
flutter pub run build_runner build --delete-conflicting-outputs

使用由Flutter生成的代码。

为了在调用GraphQL服务器时遇到错误时抛出异常,我编写了一些复杂的代码,但如果简化该部分,代码将如下所示。

import 'package:artemis/artemis.dart';
import 'package:flutter/material.dart';
import 'package:flutter_graphql_artemis/gql/generated/generated.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const PokemonListView(),
    );
  }
}

class PokemonListView extends StatelessWidget {
  const PokemonListView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pokemon List'),
      ),
      body: FutureBuilder<List<PokemonFieldMixin?>?>(
        future: searchPokemons(),
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          final pokemons = snapshot.data;
          if (pokemons == null) {
            return const Center(child: Text('データを取得できませんでした.'));
          }
          return ListView.separated(
            itemCount: pokemons.length,
            separatorBuilder: (context, index) {
              return const Divider(height: 0.5);
            },
            itemBuilder: (context, index) {
              final pokemon = pokemons[index];
              if (pokemon == null) {
                return const SizedBox();
              }
              return ListTile(
                leading: CircleAvatar(
                  backgroundImage: NetworkImage(pokemon.image!),
                  backgroundColor: Colors.grey,
                ),
                // GraphQLを使用することによって補完が効く+データ型がString?であることも分かる
                title: Text(pokemon.name!),
                subtitle: Text('Type: ${pokemon.types!.join(',')}'),
              );
            },
          );
        },
      ),
    );
  }

  // fragmentで分けておいたのでPokemonFieldMixinと宣言できる
  Future<List<PokemonFieldMixin?>?> searchPokemons() async {
    final _httpLink =
        Uri.parse('https://graphql-pokemon2.vercel.app').toString();
    final client = ArtemisClient(_httpLink);
    // SearchPokemonsQuery()がクエリから自動生成した部分
    final response = await client.execute(SearchPokemonsQuery());
    return response.data!.pokemons;
  }
}

The general flow is outlined as follows: 大致的流程如下。

    1. 使用FutureBuilder向Pokemon GraphQL服务器发出查询

 

    1. 在收到响应之前,显示加载画面

 

    一旦收到响应,使用该数据创建ListView。

我将在这里写下我想要关注的要点。

由于可以自动生成代码从查询中,所以在Dart中不需要费力地创建查询字符串。

我以前在不使用 Artemis 时努力尝试使用 Dart 的 String 编写查询,但这个过程非常痛苦,不推荐这种方法。随着查询所需参数的增加,使用字符串管理查询变得越来越困难。尽管初始设置可能有些麻烦,但我建议使用自动生成的方式。

过去,我曾经实现了一种将变量嵌入字符串的方法,但这样做非常困难。

  final String _defaultProperty = '''
    id
    name
    members {
      ${UserRepository.defaultProperty}
    }
  ''';
  final String _defaultGetQuery = r'''
    query{
      teams(
        first: 10
        ${queries}
      ){
        data{
          ${defaultProperty}
        }
      }
    }
  ''';

请用中文将以下内容改述,只需要一种选项:

In my opinion, the best way to learn a new language is by immersing oneself in the culture of the country where the language is spoken. This includes interacting with native speakers, reading books and watching movies in the target language, and practicing speaking and writing as much as possible.

通过自动生成的代码,能清楚地了解响应的数据结构和数据类型。

我理解下面的部分是指调用SearchPokemonsQuery()后,数据下面有一个类型为List<PokemonFieldMixin?>的列表,对吗?

  Future<List<PokemonFieldMixin?>?> searchPokemons() async {
    final _httpLink =
        Uri.parse('https://graphql-pokemon2.vercel.app').toString();
    final client = ArtemisClient(_httpLink);
    // SearchPokemonsQuery()がクエリから自動生成した部分
    final response = await client.execute(SearchPokemonsQuery());
    return response.data!.pokemons;
  }

进一步观察自动生成的代码,可以放心地使用PokemonFieldMixin的属性,因为它们都明确地声明了类型。如果没有自动生成,将不得不将所有属性从dynamic类型转换,所以我建议使用自动生成。

mixin PokemonFieldMixin {
  late String id;
  String? name;
  String? image;
  List<String?>? types;
}

最终、最后、最后一段

你认为如何呢?我希望你觉得在Flutter中使用GraphQL比想象中容易和方便。

如果您对此感兴趣,请务必试着运行一下,我已提供了一些示例代码。

那么,祝你的Flutter生活愉快!

广告
将在 10 秒后关闭
bannerAds