Day 23 | 在Flutter里串接restful api - 我不使用HttpClient了 jojo

这篇文主要是介绍在 Flutter 中如何串接 restful api ,主要是使用 Dio(意外的跟这个系列文题目切题) 这个套件以及搭配几个处理JSON资料的套件。

为什麽是选择 Dio

基本上 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 里面,

https://ithelp.ithome.com.tw/upload/images/20211006/20112906boe5SdC8Nd.png

接下来几个选项要注意:

  1. 最左边的上面可以填入model name 这里就填 users
  2. 右边选单得第一个选项选择 Dart
  3. 倒数第二个选项打开,这是要给一个叫做 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 封装

接下来就要来真正使用 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

  1. BaseOptions 就是可以设定的一些共用设定

    这边就将baseUrl设定好而已

  2. initializeInterceptors

    就是让我们的 _dio 去初始化的「拦截器」的function

    里面会有几个拦截点 onRequestonResponseonError 这边应该看名字就知道作用了,透过拦截器我们能在这些时间点做一些操作,但目前就先写这样就好。

然後会看到 _requestget ,我这边的规划是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 这个方法,主要就是两个参数 futurebuilder

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,然後实作一些小功能。


参考资料:

  1. https://juejin.cn/post/6844904190838325262
  2. https://zhuanlan.zhihu.com/p/352527964
  3. https://github.com/smanager-technology/sManager-Online-Payment-Flutter

<<:  D22 - 「不断线的侏罗纪」:很久很久以前的侏罗纪

>>:  [Day 23 - Redux] React + Redux = React-redux

DAY 22 Big Data 5Vs – Variety(速度) Kinesis (2)

接续介绍Kinesis家族中其它更实用的资料分析服务: 进入Kinesis服务首页可以看到这三个常常...

[Day1] MacBook及周边选购心得

前言 在铁人赛开始报名前刚好笔电坏了,一直以来用的都是 Windows系统,笔电也换了几台,这次决定...

【程序】我要加薪 转生成恶役菜鸟工程师避免 Bad End 的 30 件事 - 18

我要加薪 稳健的专业技术 对任务有充分的分析与计划能力 具有更全面的眼光及角度来制定任务计画 优化...

4套最佳 Instagram 影片下载器-PC端〖2022亲测〗

作为最受欢迎的社交媒体平台之一,Instagram 绝对是在线分享媒体的最佳场所,例如照片、视频、直...

D-6. Model scope & 建立搜索功能

建立搜索用gem 'ransack'不好吗? 完整又便利。 真的.....但是有些小东西,自己刻一个...