iT邦幫忙

2021 iThome 鐵人賽

DAY 23
0
Modern Web

Flutter web 的奇妙冒險系列 第 23

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: Sincere@april.biz, 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

上一篇
Day 22 | 狀態管理套件 MobX - 基本使用
下一篇
Day 24 | 在flutter 中串接 restful api - MobX的非同步操作
系列文
Flutter web 的奇妙冒險30

尚未有邦友留言

立即登入留言