持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
GraphQL是一个用于API的查询语言,它可以使客户端准确地获得所需的数据,没有任何冗余。在Flutter项目中怎么使用Graphql呢?我们需要借助graphql-flutter插件
Tip: 这里以4.0.1为例
1. 添加依赖
首先添加到pubspec.yaml
然后我们再看看graphql-flutter(API)有什么,以及我们该怎么用。
2.重要API
GraphQLClient
client实例方法几乎跟apollo-client一致,如query 、mutate 、 subscribe,也有些许差别的方法watchQuery 、 watchMutation 等,后面具体介绍使用区别
Link
- 作为抽象类,提供了Link.concat 、 Link.from的等factory函数,如上
graphql-flutter里基于Link实现了一些比较使用的类,如下
HttpLink
- 设置请求地址,默认header等
AuthLink
- 通过函数的形式设置Authentication
扫描二维码关注公众号,回复:
14295548 查看本文章

ErrorLink
- 设置错误拦截
DedupeLink
- 请求去重
GraphQLCache
- 配置实体缓存,官方推荐使用 HiveStore 配置持久缓存
- HiveStore在项目中关于环境是Web还是App需要作判断,所以我们需要一个方法
综上各个Link以及Cache构成了Client,我们稍加对这些API做一个封装,以便在项目复用。
3.基本封装
- 代码及释义如下
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:graphql/client.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:path_provider/path_provider.dart'
show getApplicationDocumentsDirectory;
import 'package:path/path.dart' show join;
import 'package:hive/hive.dart' show Hive;
class Gql {
final String source;
final String uri;
final String token;
final Map<String, String> header;
HttpLink httpLink;
AuthLink authLink;
ErrorLink errorLink;
GraphQLCache cache;
GraphQLClient client;
String authHeaderKey = 'token';
String sourceKey = 'source';
Gql({
@required this.source,
@required this.uri,
this.token,
this.header = const {},
}) {
// 设置url,复写传入header
httpLink = HttpLink(uri, defaultHeaders: {
sourceKey: source,
...header,
});
// 通过复写getToken动态设置auth
authLink = AuthLink(getToken: getToken, headerKey: authHeaderKey);
// 错误拦截
errorLink = ErrorLink(
onGraphQLError: onGraphQLError,
onException: onException,
);
// 设置缓存
cache = GraphQLCache(store: HiveStore());
client = GraphQLClient(
link: Link.from([
DedupeLink(), // 请求去重
errorLink,
authLink,
httpLink,
]),
cache: cache,
);
}
static Future<void> initHiveForFlutter({
String subDir,
Iterable<String> boxes = const [HiveStore.defaultBoxName],
}) async {
if (!kIsWeb) { // 判断App获取path,初始化
var appDir = await getApplicationDocumentsDirectory(); // 获取文件夹路径
var path = appDir.path;
if (subDir != null) {
path = join(path, subDir);
}
Hive.init(path);
}
for (var box in boxes) {
await Hive.openBox(box);
}
}
FutureOr<String> getToken() async => null;
void _errorsLoger(List<GraphQLError> errors) {
errors.forEach((error) {
print(error.message);
});
}
// LinkError处理函数
Stream<Response> onException(
Request req,
Stream<Response> Function(Request) _,
LinkException exception,
) {
if (exception is ServerException) { // 服务端错误
_errorsLoger(exception.parsedResponse.errors);
}
if (exception is NetworkException) { // 网络错误
print(exception.toString());
}
if (exception is HttpLinkParserException) { // http解析错误
print(exception.originalException);
print(exception.response);
}
return _(req);
}
// GraphqlError
Stream<Response> onGraphQLError(
Request req,
Stream<Response> Function(Request) _,
Response res,
) {
// print(res.errors);
_errorsLoger(res.errors); // 处理返回错误
return _(req);
}
}
复制代码
4. 基本使用
- main.dart
void main() async {
await Gql.initHiveForFlutter(); // 初始化HiveBox
runApp(App());
}
复制代码
- clent.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
const codeMessage = {
401: '登录失效,',
403: '用户已禁用',
500: '服务器错误',
503: '服务器错误',
};
// 通过复写,实现错误处理与token设置
class CustomGgl extends Gql {
CustomGgl({
@required String source,
@required String uri,
String token,
Map<String, String> header = const {},
}) : super(source: source, uri: uri, token: token, header: header);
String authHeaderKey = 'token';
@override
FutureOr<String> getToken() async { // 设置token
final sharedPref = await SharedPreferences.getInstance();
return sharedPref.getString(authHeaderKey);
}
@override
Stream<Response> onGraphQLError( // 错误处理并给出提示
Request req,
Stream<Response> Function(Request) _,
Response res,
) {
res.errors.forEach((error) {
final num code = error.extensions['exception']['status'];
Toast.error(message: codeMessage[code] ?? error.message);
print(error);
});
return _(req);
}
}
// 创建ccClient
final Gql ccGql = CustomGgl(
source: 'cc',
uri: 'https://xxx/graphql',
header: {
'header': 'xxxx',
},
);
复制代码
- demo.dart
import 'package:flutter/material.dart';
import '../utils/client.dart';
import '../utils/json_view/json_view.dart';
import '../models/live_bill_config.dart';
import '../gql_operation/gql_operation.dart';
class GraphqlDemo extends StatefulWidget {
GraphqlDemo({Key key}) : super(key: key);
@override
_GraphqlDemoState createState() => _GraphqlDemoState();
}
class _GraphqlDemoState extends State<GraphqlDemo> {
ObservableQuery observableQuery;
ObservableQuery observableMutation;
Map<String, dynamic> json;
num pageNum = 1;
num pageSize = 10;
@override
void initState() {
super.initState();
Future.delayed(Duration(), () {
initObservableQuery();
initObservableMutation();
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 10.0,
runSpacing: 10.0,
children: [
RaisedButton(
onPressed: getLiveBillConfig,
child: Text('Basic Query'),
),
RaisedButton(
onPressed: sendPhoneAuthCode,
child: Text('Basic Mutation'),
),
RaisedButton(
onPressed: () {
pageNum++;
observableQuery.fetchMore(FetchMoreOptions(
variables: {
'pageNum': pageNum,
'pageSize': pageSize,
},
updateQuery: (prev, newData) => newData,
));
},
child: Text('Watch Query'),
),
RaisedButton(
onPressed: () {
observableMutation.fetchResults();
},
child: Text('Watch Mutation'),
),
],
),
Divider(),
if (json != null)
SingleChildScrollView(
child: JsonView.map(json),
scrollDirection: Axis.horizontal,
),
],
),
);
}
@override
dispose() {
super.dispose();
observableQuery.close();
}
void getLiveBillConfig() async {
Toast.loading();
try {
final QueryResult result = await ccGql.client.query(QueryOptions(
document: gql(LIVE_BILL_CONFIG),
fetchPolicy: FetchPolicy.noCache,
));
final liveBillConfig =
result.data != null ? result.data['liveBillConfig'] : null;
if (liveBillConfig == null) return;
setState(() {
json = LiveBillConfig.fromJson(liveBillConfig).toJson();
});
} finally {
if (Toast.loadingType == ToastType.loading) Toast.dismiss();
}
}
void sendPhoneAuthCode() async {
Toast.loading();
try {
final QueryResult result = await ccGql.client.mutate(MutationOptions(
document: gql(SEND_PHONE_AUTH_CODE),
fetchPolicy: FetchPolicy.cacheAndNetwork,
variables: {
'phone': '15883300888',
'authType': 2,
'platformName': 'Backend'
},
));
setState(() {
json = result.data;
});
} finally {
if (Toast.loadingType == ToastType.loading) Toast.dismiss();
}
}
void initObservableQuery() {
observableQuery = ccGql.client.watchQuery(
WatchQueryOptions(
document: gql(GET_EMPLOYEE_CONFIG),
variables: {
'pageNum': pageNum,
'pageSize': pageSize,
},
),
);
observableQuery.stream.listen((QueryResult result) {
if (!result.isLoading && result.data != null) {
if (result.isLoading) {
Toast.loading();
return;
}
if (Toast.loadingType == ToastType.loading) Toast.dismiss();
setState(() {
json = result.data;
});
}
});
}
void initObservableMutation() {
observableMutation = ccGql.client.watchMutation(
WatchQueryOptions(
document: gql(LOGIN_BY_AUTH_CODE),
variables: {
'phone': '15883300888',
'authCodeType': 2,
'authCode': '5483',
'statisticInfo': {'platformName': 'Backend'},
},
),
);
observableMutation.stream.listen((QueryResult result) {
if (!result.isLoading && result.data != null) {
if (result.isLoading) {
Toast.loading();
return;
}
if (Toast.loadingType == ToastType.loading) Toast.dismiss();
setState(() {
json = result.data;
});
}
});
}
}
复制代码
总结
这篇文章介绍了如何在Flutter项目中简单快速的使用GraphQL。并实现了一个简单的Demo。但是上面demo将UI和数据绑定在一起,导致代码耦合性很高。在实际的公司项目中,我们都会将数据和UI进行分离,常用的做法就是将GraphQL的 ValueNotifier client 调用封装到VM层中,然后在Widget中把VM数据进行绑定操作。网络上已经有大量介绍Provider|Bloc|GetX的文章,这里以介绍GraphQL使用为主,就不再赘述了。