Flutter GraphQL请求插件custom_graphql_flutter的使用

Flutter GraphQL 请求插件 custom_graphql_flutter 的使用

在本指南中,我们将详细介绍如何在 Flutter 中使用 custom_graphql_flutter 插件来执行 GraphQL 请求。我们将涵盖安装、基本用法、查询(包括分页)、突变(包括乐观突变)以及订阅。

引言

custom_graphql_flutter 提供了一套用于 graphql/client.dart 的 Flutter API 和小部件。它们在 GitHub 上共同开发,您可以在那里找到更深入的例子。我们也有一个活跃的社区在 Discord 上。

本指南主要关注设置、小部件以及 Flutter 特定的注意事项。对于 graphql API 的更多详细信息,请参阅 graphql 的 README。

有用的章节

  • 深度链接指南
  • 直接缓存访问
  • 缓存写入严格性
  • 策略
  • 异常
  • AWS AppSync 支持
  • GraphQL 上传
  • 构建时解析 ASTs

有用的 API 文档

  • GraphQLCache
  • GraphQLDataProxy API 文档(直接缓存访问)

安装

首先,在 pubspec.yaml 文件中添加依赖:

$ flutter pub add custom_graphql_flutter

然后在 Dart 代码中导入:

import 'package:custom_graphql_flutter/graphql_flutter.dart';

使用

为了连接到 GraphQL 服务器,我们需要创建一个 GraphQLClientGraphQLClient 需要 cachelink 两个参数才能初始化。

在我们的示例中,我们将使用 Github 公共 API。我们将使用 HttpLink 并将其与 AuthLink 连接起来以附加我们的 GitHub 访问令牌。对于缓存,我们将使用 GraphQLCache

import 'package:custom_graphql_flutter/graphql_flutter.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  // 我们将使用 HiveStore 进行持久化,
  // 因此我们需要初始化 Hive。
  await initHiveForFlutter();
  
  final HttpLink httpLink = HttpLink(
    'https://api.github.com/graphql',
  );

  final AuthLink authLink = AuthLink(
    getToken: () async => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
    // 或者
    // getToken: () => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
  );

  final Link link = authLink.concat(httpLink);

  ValueNotifier<GraphQLClient> client = ValueNotifier(
    GraphQLClient(
      link: link,
      // 默认存储是 InMemoryStore,它不会持久化到磁盘
      cache: GraphQLCache(store: HiveStore()),
    ),
  );

  runApp(MyApp(client: client));
}

GraphQL Provider

为了使用客户端,您的 QueryMutation 小部件必须包装在 GraphQLProvider 小部件中。

建议将 MaterialApp 包裹在 GraphQLProvider 小部件中。

import 'package:flutter/material.dart';
import 'package:custom_graphql_flutter/graphql_flutter.dart';

class MyApp extends StatelessWidget {
  final ValueNotifier<GraphQLClient> client;

  MyApp({required this.client});

  @override
  Widget build(BuildContext context) {
    return GraphQLProvider(
      client: client,
      child: MaterialApp(
        title: 'Flutter Demo',
        home: MyHomePage(),
      ),
    );
  }
}

查询

创建查询就像创建一个多行字符串一样简单:

String readRepositories = """
  query ReadRepositories(\$nRepositories: Int!) {
    viewer {
      repositories(last: \$nRepositories) {
        nodes {
          id
          name
          viewerHasStarred
        }
      }
    }
  }
""";

在您的小部件中:

import 'package:flutter/material.dart';
import 'package:custom_graphql_flutter/graphql_flutter.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Query(
      options: QueryOptions(
        document: gql(readRepositories),
        variables: {
          'nRepositories': 50,
        },
        pollInterval: const Duration(seconds: 10),
      ),
      builder: (QueryResult result, { VoidCallback? refetch, FetchMore? fetchMore }) {
        if (result.hasException) {
          return Text(result.exception.toString());
        }

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

        List? repositories = result.data?['viewer']?['repositories']?['nodes'];

        if (repositories == null) {
          return const Text('No repositories');
        }

        return ListView.builder(
          itemCount: repositories.length,
          itemBuilder: (context, index) {
            final repository = repositories[index];
            return Text(repository['name'] ?? '');
          },
        );
      },
    );
  }
}

或者,如果您更喜欢使用 flutter-hooks,您可以这样写:

import 'package:flutter/material.dart';
import 'package:custom_graphql_flutter/graphql_flutter.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class MyHomePage extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final readRepositoriesResult = useQuery(
      QueryOptions(
        document: gql(readRepositories),
        variables: {
          'nRepositories': 50,
        },
        pollInterval: const Duration(seconds: 10),
      ),
    );
    final result = readRepositoriesResult.result;

    if (result.hasException) {
      return Text(result.exception.toString());
    }

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

    List? repositories = result.data?['viewer']?['repositories']?['nodes'];

    if (repositories == null) {
      return const Text('No repositories');
    }

    return ListView.builder(
      itemCount: repositories.length,
      itemBuilder: (context, index) {
        final repository = repositories[index];
        return Text(repository['name'] ?? '');
      },
    );
  }
}

分页

您可以使用 fetchMore() 函数在 Query 构建器中执行分页。fetchMore() 函数允许您运行一个新的 GraphQL 操作并将新结果合并到原始结果中。此外,您可以重用原始查询的某些方面,例如查询或变量。

为了使用 fetchMore() 函数,您需要首先定义 FetchMoreOptions 变量用于新的查询。

final Map pageInfo = result.data['search']['pageInfo'];
final String fetchMoreCursor = pageInfo['endCursor'];

FetchMoreOptions opts = FetchMoreOptions(
  variables: {'cursor': fetchMoreCursor},
  updateQuery: (previousResultData, fetchMoreResultData) {
    final List<dynamic> repos = [
      ...previousResultData['search']['nodes'] as List<dynamic>,
      ...fetchMoreResultData['search']['nodes'] as List<dynamic>
    ];

    fetchMoreResultData['search']['nodes'] = repos;
    return fetchMoreResultData;
  },
);

然后,调用 fetchMore() 函数并传递您定义的 FetchMoreOptions 变量。

RaisedButton(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text("Load More"),
    ],
  ),
  onPressed: () {
    fetchMore(opts);
  },
)

突变

同样,首先创建一个突变字符串:

String addStar = """
  mutation AddStar(\$starrableId: ID!) {
    addStar(input: {starrableId: \$starrableId}) {
      starrable {
        viewerHasStarred
      }
    }
  }
""";

突变的语法与查询非常相似。唯一的区别是构建函数的第一个参数是一个突变函数。只需调用它即可触发突变。

Mutation(
  options: MutationOptions(
    document: gql(addStar),
    update: (GraphQLDataProxy cache, QueryResult result) {
      return cache;
    },
    onCompleted: (dynamic resultData) {
      print(resultData);
    },
  ),
  builder: (
    RunMutation runMutation,
    QueryResult result,
  ) {
    return FloatingActionButton(
      onPressed: () => runMutation({
        'starrableId': '<A_STARTABLE_REPOSITORY_ID>',
      }),
      tooltip: 'Star',
      child: Icon(Icons.star),
    );
  },
);

对应的钩子实现如下:

import 'package:flutter/material.dart';
import 'package:custom_graphql_flutter/graphql_flutter.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class MyHomePage extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final addStarMutation = useMutation(
      MutationOptions(
        document: gql(addStar),
        update: (GraphQLDataProxy cache, QueryResult result) {
          return cache;
        },
        onCompleted: (dynamic resultData) {
          print(resultData);
        },
      ),
    );
    return FloatingActionButton(
      onPressed: () => addStarMutation.runMutation({
        'starrableId': '<A_STARTABLE_REPOSITORY_ID>',
      }),
      tooltip: 'Star',
      child: Icon(Icons.star),
    );
  }
}

graphql 还提供了文件上传支持。

乐观突变

通过向 RunMutation 传递 optimisticResultGraphQLCache 允许进行乐观突变。它将两次调用 update(GraphQLDataProxy cache, QueryResult result)(一次急切地使用 optimisticResult),并重新广播所有查询以使用乐观缓存状态。

有关如何与 update 提供的 proxy 进行接口的完整和注释详尽的说明,可以查看 GraphQLDataProxy API 文档。

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        toggleStar(
          { 'starrableId': repository['id'] },
          optimisticResult: {
            'action': {
              'starrable': {'viewerHasStarred': !starred}
            }
          },
        );
      },
    );
  }
}

订阅

订阅的语法再次类似于查询,但它利用了 WebSocket 和 dart Streams 来从服务器提供实时更新。

为了使用订阅,订阅消费链接必须从您的 HttpLink 或其他终止链接路由中分离出来:

link = Link.split((request) => request.isSubscription, websocketLink, link);

然后,您可以订阅服务器模式提供的任何 subscription

final subscriptionDocument = gql(
  r'''
    subscription reviewAdded {
      reviewAdded {
        stars, commentary, episode
      }
    }
  ''',
);

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Subscription(
          options: SubscriptionOptions(
            document: subscriptionDocument,
          ),
          builder: (result) {
            if (result.hasException) {
              return Text(result.exception.toString());
            }

            if (result.isLoading) {
              return Center(
                child: const CircularProgressIndicator(),
              );
            }
            return ResultAccumulator.appendUniqueEntries(
              latest: result.data,
              builder: (context, {results}) => DisplayReviews(
                reviews: results.reversed.toList(),
              ),
            );
          }
        ),
      )
    );
  }
}

对应的钩子实现如下:

import 'package:flutter/material.dart';
import 'package:custom_graphql_flutter/graphql_flutter.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class MyHomePage extends HooksWidget {
  @override
  Widget build(BuildContext context) {
    final subscriptionResult = useSubscription(
      SubscriptionOptions(
        document: subscriptionDocument,
      ),
    );
    
    Widget child;
    if (subscriptionResult.hasException) {
      child = Text(subscriptionResult.exception.toString());
    } else if (subscriptionResult.isLoading) {
      child = Center(
        child: const CircularProgressIndicator(),
      );
    } else {
      child = ResultAccumulator.appendUniqueEntries(
        latest: subscriptionResult.data,
        builder: (context, {results}) => DisplayReviews(
          reviews: results.reversed.toList(),
        ),
      );    
    }
    return Scaffold(
      body: Center(child: child)
    );
  }
}

其他钩子

除了 useMutationuseQueryuseSubscription,该包还包含以下钩子:

final client = useGraphQLClient(); // 获取当前客户端
final observableQuery = useWatchQuery(WatchQueryOptions(...)); // 监视查询
final mutationObservableQuery = useWatchMutation(WatchQueryOptions(...)); // 监视突变

GraphQL Consumer

如果您想直接使用 client,比如其直接缓存更新方法,您可以使用 GraphQLConsumer 从任何 GraphQLProvider 下降的 context 中获取它:

import 'package:flutter/material.dart';
import 'package:custom_graphql_flutter/graphql_flutter.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GraphQLConsumer(
      builder: (GraphQLClient client) {
        // 对客户端做些什么

        return Container(
          child: Text('Hello world'),
        );
      },
    );
  }
}

代码生成

该包本身不支持代码生成,但 graphql_codegen 做到了!

该包生成钩子和选项,消除了序列化的麻烦,并通过类型安全给您信心。

例如,通过创建 .graphql 文件:

query ReadRepositories($nRepositories: Int!) {
  viewer {
    repositories(last: $nRepositories) {
      nodes {
        id
        name
      }
    }
  }
}

构建后,您可以通过钩子在代码中查询:

final queryResult = useQueryReadRepositories(
  OptionsQueryReadRepositories(
    variables: VariablesQueryReadRepositories(
      nRepositories: 10
    )
  )
);
if (queryResult.result.hasException) {
  return Text(queryResult.result.exception.toString());
}
if (queryResult.result.isLoading) {
  return Text(text: "LOADING");
}
final data = queryResult.result.parsedData;

return Column(
  children: data?.viewer.repositories.nodes.map((node) => Text(text: node.name)).toList() ?? [],
);

更多关于Flutter GraphQL请求插件custom_graphql_flutter的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter GraphQL请求插件custom_graphql_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中使用GraphQL进行网络请求时,custom_graphql_flutter 是一个非常灵活的插件。它允许你自定义GraphQL请求并处理响应。以下是使用 custom_graphql_flutter 插件的详细步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 custom_graphql_flutter 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  custom_graphql_flutter: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 初始化 GraphQL Client

在你的应用程序中,你需要初始化一个 GraphQL 客户端。通常,你可以在 main.dart 文件中完成这个步骤。

import 'package:custom_graphql_flutter/custom_graphql_flutter.dart';
import 'package:flutter/material.dart';

void main() {
  final HttpLink httpLink = HttpLink(
    'https://your-graphql-endpoint.com/graphql',
  );

  final GraphQLClient client = GraphQLClient(
    link: httpLink,
    cache: GraphQLCache(),
  );

  runApp(MyApp(client: client));
}

class MyApp extends StatelessWidget {
  final GraphQLClient client;

  MyApp({required this.client});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter GraphQL Demo',
      home: GraphQLProvider(
        client: client,
        child: MyHomePage(),
      ),
    );
  }
}

3. 使用 GraphQLProvider

GraphQLProvider 是一个 InheritedWidget,它允许你在整个应用程序中访问 GraphQL 客户端。你可以在任何需要的地方使用 GraphQLProvider.of(context) 来获取客户端。

4. 发起 GraphQL 请求

你可以使用 QueryMutation 组件来发起 GraphQL 请求。以下是一个使用 Query 组件的示例:

import 'package:custom_graphql_flutter/custom_graphql_flutter.dart';
import 'package:flutter/material.dart';

class MyHomePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    final client = GraphQLProvider.of(context).value;

    return Scaffold(
      appBar: AppBar(
        title: Text('GraphQL Demo'),
      ),
      body: Query(
        options: QueryOptions(
          document: gql('''
            query GetPosts {
              posts {
                id
                title
                body
              }
            }
          '''),
        ),
        builder: (QueryResult result, { VoidCallback? refetch, FetchMore? fetchMore }) {
          if (result.hasException) {
            return Text('Error: ${result.exception.toString()}');
          }

          if (result.isLoading) {
            return Center(child: CircularProgressIndicator());
          }

          final posts = result.data?['posts'] as List<dynamic>?;

          return ListView.builder(
            itemCount: posts?.length ?? 0,
            itemBuilder: (context, index) {
              final post = posts?[index];
              return ListTile(
                title: Text(post['title']),
                subtitle: Text(post['body']),
              );
            },
          );
        },
      ),
    );
  }
}

5. 使用 Mutation

如果你需要执行一个 GraphQL 变更操作,你可以使用 Mutation 组件。以下是一个示例:

import 'package:custom_graphql_flutter/custom_graphql_flutter.dart';
import 'package:flutter/material.dart';

class AddPostPage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Add Post'),
      ),
      body: Mutation(
        options: MutationOptions(
          document: gql('''
            mutation AddPost($title: String!, $body: String!) {
              addPost(title: $title, body: $body) {
                id
                title
                body
              }
            }
          '''),
        ),
        builder: (RunMutation runMutation, QueryResult? result) {
          return Column(
            children: [
              TextField(
                decoration: InputDecoration(labelText: 'Title'),
                onChanged: (value) {
                  // 处理标题输入
                },
              ),
              TextField(
                decoration: InputDecoration(labelText: 'Body'),
                onChanged: (value) {
                  // 处理内容输入
                },
              ),
              ElevatedButton(
                onPressed: () {
                  runMutation({
                    'title': 'Example Title',
                    'body': 'Example Body',
                  });
                },
                child: Text('Add Post'),
              ),
              if (result != null && result.hasException)
                Text('Error: ${result.exception.toString()}'),
              if (result != null && result.isLoading)
                Center(child: CircularProgressIndicator()),
              if (result != null && result.data != null)
                Text('Post added successfully!'),
            ],
          );
        },
      ),
    );
  }
}

6. 处理错误和加载状态

QueryMutation 组件中,你可以通过 QueryResult 对象来处理错误和加载状态。result.hasException 可以用于检查是否有错误发生,result.isLoading 可以用于检查请求是否仍在加载中。

7. 自定义请求

custom_graphql_flutter 允许你自定义请求的各个方面,包括请求头、缓存策略等。你可以通过 HttpLink 或其他 Link 实现来配置这些选项。

final AuthLink authLink = AuthLink(
  getToken: () async => 'Bearer $yourToken',
);

final Link link = authLink.concat(httpLink);

final GraphQLClient client = GraphQLClient(
  link: link,
  cache: GraphQLCache(),
);

8. 缓存管理

GraphQLCache 是默认的缓存实现,你可以通过它来管理缓存。你也可以自定义缓存策略,或者使用其他缓存实现。

9. 使用 Subscription

如果你需要实时数据,你可以使用 Subscription 组件来订阅 GraphQL 数据。

import 'package:custom_graphql_flutter/custom_graphql_flutter.dart';
import 'package:flutter/material.dart';

class RealTimeDataPage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Real Time Data'),
      ),
      body: Subscription(
        options: SubscriptionOptions(
          document: gql('''
            subscription OnPostAdded {
              postAdded {
                id
                title
                body
              }
            }
          '''),
        ),
        builder: (QueryResult result) {
          if (result.hasException) {
            return Text('Error: ${result.exception.toString()}');
          }

          if (result.isLoading) {
            return Center(child: CircularProgressIndicator());
          }

          final post = result.data?['postAdded'];

          return ListTile(
            title: Text(post['title']),
            subtitle: Text(post['body']),
          );
        },
      ),
    );
  }
}
回到顶部