这篇文主要是介绍在 Flutter 中如何串接 restful api ,主要是使用 Dio(意外的跟这个系列文题目切题) 这个套件以及搭配几个处理JSON资料的套件。
基本上 Dart 有提供自己的解决方案像是内建的 HttpClient
或者是 Dart 团队自己写的套件 http
,其实如果只是简单的call api 之类的行为这三者其实没什麽差,但 dio 提供比较多好用的功能,但我自己觉得 dio 最大的好处是可以很简单的使用,也可以封装的很复杂。
这次就直接新建立一个专案,详细流程就不再说明一次了。
然後安装这次会用的套件:
dependencies:
mobx: ^2.0.4
flutter_mobx: ^2.0.2
freezed_annotation: ^0.14.3
dio: ^4.0.0
dev_dependencies:
mobx_codegen: ^2.0.3
build_runner: ^2.1.4
freezed: ^0.14.5
json_serializable: ^5.0.2
这次是采用 https://jsonplaceholder.typicode.com/ 的免费API server,但我们还需要对回传资料经过json反序列化的处理,所以会有几个问题。
那首先我们先来将JSON资料转成 Dart code。
这边我们会用https://app.quicktype.io/ 这个线上工具帮我们达成这件事情,但这个工具目前有一个小缺点,就是他还不支援 null safety dart 所以我们後续还是额外加工,当然这件事有其他解决方案,但要操作一堆套件就觉得有点麻烦,为了快速实作demo就不使用其他方法了。
我们先来实作将 /users
的资料转成 Dart code看看。
首先先到 https://jsonplaceholder.typicode.com/users 这个页面会发现他有一个JSON Data 就把整个都复制下来,然後贴上上面的 quicktype 里面,
接下来几个选项要注意:
freezed
的套件使用的。接下来就复制到我们的编辑器上:
新增一个叫做 user.dart的档案然後贴上
// To parse this JSON data, do
//
// final users = usersFromJson(jsonString);
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:convert';
part 'users.freezed.dart';
part 'users.g.dart';
List<Users> usersFromJson(String str) =>
List<Users>.from(json.decode(str).map((x) => Users.fromJson(x)));
String usersToJson(List<Users> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
@freezed
abstract class Users with _$Users {
const factory Users({
int id,
String name,
String username,
String email,
Address address,
String phone,
String website,
Company company,
}) = _Users;
factory Users.fromJson(Map<String, dynamic> json) => _$UsersFromJson(json);
}
// 底下省略
// 底下省略
// 底下省略
这边会有好几个 abstract class
要处理,最主要的工作就是将factory
的每个type都标成nullable ,像是这样:
const factory Users({
int? id,
String? name,
String? username,
String? email,
Address? address,
String? phone,
String? website,
Company? company,
}) = _Users;
其他 abstract class
就如法炮制,这边的话就可以用 command + D
来选取Type 来快速编辑(但别按太快选到 factory
以外的就是了) 。
都用好後就可以使用 build_runner 来产生我们要的code了。
这边说一下 freezed
的功用就是可以从我们的model class 来产生可以去做JSON反序列化的 code。让我们从api server拿到 JSON字串时可以经过这个code 变成有type保障的物件。
至此我们做了这些事情:
拿到JSON资料 → 转成 model class →经过 freezed
来产生JSON反序列化的方法。
接下来就要来真正使用 Dio
了,以下的封装形式是我参考网路文章及别人的github的:
import 'package:dio/dio.dart';
class HttpService {
late Dio _dio;
final baseUrl = "https://jsonplaceholder.typicode.com/";
HttpService() {
_dio = Dio(BaseOptions(
baseUrl: baseUrl,
));
initializeInterceptors();
}
Future<Response> _request(String path, {required String method}) async {
Response response;
try {
response = await _dio.request(path, options: Options(method: method));
} on DioError catch (e) {
print(e.message);
throw Exception(e.message);
}
return response;
}
Future<Response> get(String path) async {
return _request(path, method: 'get');
}
initializeInterceptors() {
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
print("${options.method} ${options.path}");
return handler.next(options);
},
onResponse: (response, handler) {
return handler.next(response);
},
onError: (DioError e, handler) {
return handler.next(e);
},
),
);
}
}
先来看这个 class
的 constructor
BaseOptions
就是可以设定的一些共用设定
这边就将baseUrl设定好而已
initializeInterceptors
就是让我们的 _dio
去初始化的「拦截器」的function
里面会有几个拦截点 onRequest
、 onResponse
、 onError
这边应该看名字就知道作用了,透过拦截器我们能在这些时间点做一些操作,但目前就先写这样就好。
然後会看到 _request
及 get
,我这边的规划是get
是给外部的呼叫介面,而内部不论哪个 method都是透过 _request
进行处理,只是我目前只有先实作get
。
我们先在 widget里宣告一个 async funtcion
Future<List<Users>> fetchData() async {
HttpService httpService = HttpService();
final response = await httpService.get('users');
final jsonStr = json.encode(response.data);
final result = usersFromJson(jsonStr);
print(result[0]);
return result;
}
我们使用了我们刚刚封装的 dio 来进行 get request ,因为我们 httpService
有封装了 baseUrl
所以在 get
这里只需要传入 'users'
就好。
之後会得到一个 response
,因为 dio 的原因所以我们还要先丢进去 json.encode
一次,後将这个 jsonStr
丢进 usersFromJson
也就是我们 users model 里的那个方法,它最後会回传 List<Users>
在我们真正与UI串接前我们先直接呼叫看看会怎样。
@override
Widget build(BuildContext context) {
fetchData();
return Scaffold(
//... 省略
)
}
print
出的第一笔:
Users(id: 1, name: Leanne Graham, username: Bret, email: [email protected], address: Address(street: Kulas Light, suite: Apt. 556, city: Gwenborough, zipcode: 92998-3874, geo: Geo(lat: -37.3159, lng: 81.1496)), phone: 1-770-736-8031 x56442, website: hildegard.org, company: Company(name: Romaguera-Crona, catchPhrase: Multi-layered client-server neural-net, bs: harness real-time e-markets))
接下来就 widget的串接了
首先会用到 FutureBuilder
这个方法,主要就是两个参数 future
、 builder
FutureBuilder<List<Users>>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('loading...');
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return Text('${snapshot.data![0].name}');
}
}
if (snapshot.hasError) {
return const Text('error!!');
}
return const Text('loading...');
})
future
就是放置我们要处理的非同步事件那我们就放上刚刚宣告的 fetchData()
,builder
则是跟一般的 build()
类似只是这里多一个参数 snapshot
他会有类似我们在处理 Future
时一样可以让我们处理各种连结状况。
所以这边就可以做出读取中、完成时或发生错误时。
如果要测试错误的情况可以将 get
里的url 随便打就会有错误状况发生。
今天的程序码:
https://github.com/zxc469469/flutter_rest_api_playground
明天开始就要再加上 MobX,然後实作一些小功能。
参考资料:
<<: D22 - 「不断线的侏罗纪」:很久很久以前的侏罗纪
>>: [Day 23 - Redux] React + Redux = React-redux
接续介绍Kinesis家族中其它更实用的资料分析服务: 进入Kinesis服务首页可以看到这三个常常...
前言 在铁人赛开始报名前刚好笔电坏了,一直以来用的都是 Windows系统,笔电也换了几台,这次决定...
我要加薪 稳健的专业技术 对任务有充分的分析与计划能力 具有更全面的眼光及角度来制定任务计画 优化...
作为最受欢迎的社交媒体平台之一,Instagram 绝对是在线分享媒体的最佳场所,例如照片、视频、直...
建立搜索用gem 'ransack'不好吗? 完整又便利。 真的.....但是有些小东西,自己刻一个...