一:前言 - 什麼是反射機制,Flutter為什麼禁用反射機制?
在Flutter中它的網路請求和資料解析稍微的比較麻煩一點,因為Flutter不支援反射機制。相信大家都看到這麼一條,就是Flutter不支援反射,那首先有一點需要我們明白的。什麼是反射?不知道大家看到這個問題的時候,有多少人腦子裡面是一下子能閃出反射的概念的,我們首先還是說說,什麼是反射機制。
反射機制簡單來說就是動態獲取類或者物件中的屬性,對於任何一個類,我們都能夠知道這個類有哪些方法和屬性。對於任何一個物件,我們都能夠對它的方法和屬性進行呼叫。我們把這種動態獲取物件資訊和呼叫物件方法的功能稱之為反射機制。要往深了去理解,的確反射機制是一個比較難的概念,這裡有一篇寫的比較好的 淺談反射機制 傳送門給大家,有興趣的看這篇應該對反射是可以有一個比較深的認知的。
大概知道之後,再說一點就是其實單純的Dart語言是支援反射機制的,只不過Flutter把它禁止了而已,那我們得追究一下 為什麼Flutter要禁止Dart的反射機制呢?
這個問題其實官網給過我們答案,我們看看官方是怎麼說的:
簡單的總結一下:由於反射預設會使用所有的程式碼,就導致在釋出應用的時候沒法去除掉未使用的程式碼,沒法顯著的優化程式的大小,所以Flutter禁用了Dart的反射機制。
二:Flutter的JSON序列化
既然我們在前面說了Flutter不支援反射機制,那它的JSON序列化又是怎樣進行的呢?
首先Flutter中基本的JSON序列化是非常簡單的,lutter有一個內建dart:convert庫,其中包含一個簡單的JSON編碼器和解碼器。但是不管是dart:convert來處理還是我們使用模型來處理,都是需要我們手動進行的,不僅僅效率比較低,出錯的概率也會比較大,在序列化的過程中可能因為一些很細小的錯誤,導致我們花費大量的時間排查其中的問題,這就對開發者是很不友好了,那有沒有什麼能幫助我們自動進行JSON的序列化處理的呢,答案也是有,下面就是我們Flutter處理JSON序列化的主角:json_serializable
首先要把json_serializable匯入到我們專案中的話,還需要一個常規和兩個開發依賴項,具體得我們看看pubspec.yaml新增的內容:
# Your other regular dependencies here json_annotation: ^4.4.0 # Your other dev_dependencies here json_serializable: ^6.1.5 build_runner: ^2.1.8
注意: 這幾個外掛的版本具體的是跟著我自己的Flutter版本變化的,它們之間版本是相互有影響的,我沒記錯在執行命令生成g.dart檔案的時候,版本不對還有錯誤產生,具體的錯誤我之前也忘記沒有收集,在這就只能大概的提一句,要真的遇上問題的小夥伴,也可以朝著這個方向去解決查詢問題。
有了這幾個外掛之後,我們接著往後面看該怎麼處理, 在官網是給我們一個User的model的處理,具體的程式碼如下,我們可以參考學習下,並且注意下程式碼裡面註釋的內容:
import 'package:json_annotation/json_annotation.dart'; // user.g.dart 將在我們執行生成命令後自動生成 part 'user.g.dart'; ///這個標註是告訴生成器,這個類是需要生成Model類的 @JsonSerializable() class User{ User(this.name, this.email); String name; String email; //不同的類使用不同的mixin即可 factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); Map<String, dynamic> toJson() => _$UserToJson(this); }
注意:還有一個關鍵詞@JsonKey,比如我們的介面返回一個欄位A,但是在模型中我們想使用欄位B代替介面返回的A,那我們就可以使用@JsonKey關鍵字。我們舉一個很現實的例子,就像你在iOS中,服務端介面給您返回一個id,你專案在使用OC的情況下是沒辦法在model中直接使用id接收的,因為關鍵字衝突,所以我們會處理成ID或者別的去接收,大概就是這麼個情況,理解就可以了。
/// Tell json_serializable that "registration_date_millis" should be /// mapped to this property. @JsonKey(name: 'id') final int goodsId;
但在具體的開發中我們也需要自己給model中寫這些程式碼嗎?是的,但我推薦給大家一個可以幫我們生成model的地方。 【我在這裡-為了便利使用json_serializable庫】
有幾個小地方需要我們注意下,標註出來了,處理完之後你需要的就只是複製了。
這樣你複製了內容,建立自己的model.dart檔案,裡面會有一些引用的錯誤,你可以不必理會,等我們處理完之後會順帶這修復的,接下來就是執行下面的命令來生成我們的序列化模板,在我們的專案根目錄下執行:
flutter packages pub run build_runner build
我們可以在需要時為我們的model生成json序列化程式碼。 這觸發了一次性構建,它通過我們的原始檔,挑選相關的併為它們生成必要的序列化程式碼。雖然這非常方便,但如果我們不需要每次在model類中進行更改時都要手動執行構建命令的話會更好。那我們有辦法持續性的生成序列化模板嗎,答案是肯定的,接下來我們再執行命令:
flutter packages pub run build_runner watch
這個命令就幫助我們在專案根目錄下執行來啟動_watcher_,只需啟動一次觀察器,然後並讓它在後臺執行,這是安全的。具體的表現就像下面的動圖一樣的,在我們建立好我們的TestModel.dart檔案之後,我們只需要儲存,後面的序列化模板(TestModel.g.dart)檔案也會隨著自己生成,這就是前面命令執行完之後的持續性生成序列化模板的作用。
這樣我們持續在建立g.dart檔案,我們的序列化準備工作也就完成了,具體的序列化的程式碼我們在下面網路請求到出局之後一起看。
三:網路請求和JSON序列化
在Flutter的網路請求外掛中,不得不提的使我們的Dio,在Pub上好評率很高,並且在GitHub也收穫了近萬Star。官方文件是這樣描述Dio的:Dio是一個強大的DartHttp請求庫,支援RestfulAPI、FormData、攔截器、請求取消、Cookie管理、檔案上傳/下載、超時、自定義介面卡等...可以說是覆蓋了所有涉及到的網路請求。 並且是國人開源的,所以我們只需要利用這個外掛就足以應付Flutter的各種網路請求需求了。關於這個外掛的具體使用我們不在這裡贅述,的確網上太多太多的資料供大家查閱。
【Dio Git地址】 前面提了這是國人開源的,大家可以翻閱中文開發文件查閱問題更方便。
這是Git給的一個例子,使用也是很簡單,但具體根據自己專案進行封裝等的就需要自己去處理。
import 'package:dio/dio.dart'; void getHttp() async { try { var response = await Dio().get('http://www.google.com'); print(response); } catch (e) { print(e); } }
下面是我們自己專案中具體的一些處理,前面說的我們處理好序列化的東西后就可以在請求到資料後直接處理成model了,重點就在
Responded<T> result = Responded<T>.fromJson(data);
static void send<T>(Request req, {SuccessObj<T>? success, SuccessObjList<T>? successList, Failure? failure}) async { try { // 建立dio Dio dio = _createDio(); // dio發起請求 var response = await _convertToDio(dio, req); // 拿到的資料做一個簡單的解碼 var data = jsonDecode(response.toString()); // 解析成我們需要的資料模型 Responded<T> result = Responded<T>.fromJson(data); if (result.code == 200) { if (result.dataArray != null) { if (successList != null) { successList(result.dataArray); } } else { if (success != null) { if (result.data != null) { success(result.data!); } else { if (failure != null) { failure(Exceptions(result.code!, "沒有相關資料!!")); } } } } } else { var exception = result.exception; // ignore: prefer_conditional_assignment if (null == exception) { exception = Exceptions(result.code!, result.message ??= ""); } if (failure != null) { failure(exception); } } } catch (e) { if (failure != null) { if (e is DioError) { failure(Exceptions.create(e)); } } } }
在我們生成的g.dart檔案中,重點就是就是我們需要的編碼和解析的方法,比如我寫的測試demo中:
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'BodyModel.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** BodyModel _$BodyModelFromJson(Map<String, dynamic> json) => BodyModel( json['userId'] as int, json['id'] as int, json['title'] as String, json['body'] as String, ); Map<String, dynamic> _$BodyModelToJson(BodyModel instance) => <String, dynamic>{ 'userId': instance.userId, 'id': instance.id, 'title': instance.title, 'body': instance.body, };
至此,關於Flutter網路請求和JSON序列化的東西我們就基本上梳理完了,小夥伴要疑問,可以留言或者私信我,一起學習探索。
編寫本文章時候的Flutter版本:
---------------------------------------------------------------
➜ mixedflutter flutter --version
Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 5464c5bac7 (3 weeks ago) • 2022-04-18 09:55:37 -0700
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2
---------------------------------------------------------------