api數據序列化爲model實例是移動開發中很常見也是很基礎的技術點,得益於運行時等動態技術在ios開發中咱們能夠藉助JSONModel或者SwiftyJSON很方便的實現序列化,對於剛剛接觸flutter的開發者來講其序列化體驗無疑是很是糟糕的。自己Dart語言是支持反射的,可是在Flutter中,Dart幾乎放棄了腳本語言動態化的特性,如不支持反射、也不支持動態建立函數等;因此序列化只有依靠攔截註解來動態生成代碼的方式實現。ios
註解是一種能夠爲代碼提供一些語義信息或元數據的標註,這在其餘語言中也很常見,在dart中常見的註解有@deprecated、@override等,註解是以@開頭的,他們能夠做用於類,函數,屬性等。git
dart中自定義註解很簡單,其實現就是一個帶有const構造函數的類github
library todo; class Todo { final String who; final String what; const Todo(this.who, this.what); }
而後就能夠這樣使用Todo這個註解了json
import 'todo.dart'; @Todo('seth', 'make this do something') void doSomething() { print('do something'); }
經過註解的方式咱們就能夠爲類或者屬性添加一個額外的數據信息,source_gen能夠攔截註解獲取並解析上下文信息,經過解析註解實現source_gen的相關Generator就能夠動態的生成代碼了;api
source_gen是封裝自build和 analyzer,並在此基礎上提供友好的api封裝。build是一個提供構建控制的庫,analyzer是提供dart語法靜態分析功能的庫,source_gen將其整合即可以實現一套基於註解的代碼生成工具。app
使用Annotation+source_gen的方式能夠便捷的生成代碼,source_gen經過攔截Annotation,解析其上下文element而後經過builder便可動態生成代碼,下面簡易的代碼生成Demo。ide
終端運行:函數
flutter create --template=package code_gen_demo
vscode打開剛剛建立的package, pubspec.yaml添加source_gen和build_runner依賴工具
dependencies: flutter: sdk: flutter source_gen: '>=0.8.0'
lib目錄下建立註解mark.dart學習
class Mark { final String name; const Mark({this.name}); }
建立代碼生成器generator.dart 負責攔截咱們的註解Mark, 解析註解的類名稱,路徑及其參數name並返回
import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; import 'package:build/build.dart'; import 'mark.dart'; class MarkGenerator extends GeneratorForAnnotation<Mark> { @override generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) { String className = element.displayName; String path = buildStep.inputId.path; String name =annotation.peek('name').stringValue; return "//$className\n//$path\n//$name"; } }
lib目錄建立構建器builder.dart, 添加一個頂級方法markBuilder供build runner解析調用
import 'package:source_gen/source_gen.dart'; import 'package:build/build.dart'; import 'mark_generator.dart'; Builder markBuilder(BuilderOptions options) => LibraryBuilder(MarkGenerator(), generatedExtension: '.mark.dart');
在package根目錄下添加build.yaml文件(buildRunner會解析其配置執行builder指定的方法),配置成剛剛建立的builder內容以下
targets: $default: builders: code_gen_demo|mark_builder: enabled: true builders: mark_builder: import: 'package:code_gen_demo/builder.dart' builder_factories: ['markBuilder'] build_extensions: { '.dart': ['.mark.dart'] } auto_apply: root_package build_to: source
import指定了builder的位置,builder_factories指定了builder的具體調用,build_extensions指定了輸入輸入文件的格式匹配,此列會生成".mark.dart"結尾的文件。
至此代碼生成相關的Annotation、 builder和Generator都準備好了,接下來咱們建立example工程來作示例
在package的根目錄下建立example工程,example是一個完整的flutter工程,執行命令:
flutter create example
在example工程中引入咱們的package, 在example的pubspec.yaml中添加依賴package,以及添加對builder_runner的依賴來執行編譯命令
dependencies: flutter: sdk: flutter code_gen_demo: path: ../ # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter build_runner: '>=0.9.1'
建立一個示例類,mark_demo.dart, 並添加Mark註解
import 'package:code_gen_demo/mark.dart'; @Mark(name: "hello") class MarkDemo { }
好了,接下來在example目錄下執行builder runner命令來爲Mark註解的mark_demo.dart生成一個相關代碼mark_demo.mark.dart
flutter packages pub run build_runner build --delete-conflicting-outputs
從新執行run builder_runner前最好先clean一下
flutter packages pub run build_runner clean
命令執行完成後就能夠看到在mark_demo.dart文件下生成了一個mark_demo.mark.dart的文件,其內容是mark_generator.dart中爲Mark這個註解建立的Generator返回的內容:
// GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** // MarkGenerator // ************************************************************************** //MarkDemo //lib/mark_demo.dart //hello
本demo源碼位置GitHub
目前在Flutter中常見的代碼生成主要應用在json序列化庫json_serializable中,在國內閒魚技術團隊使用這一技術實現了一套router的路由映射解決方案annotation_route,感興趣的能夠看看。
做爲學習我參考了閒魚的annotation_route實現了一個簡單的Flutter頁面路由匹配方案easy_router,不一樣於閒魚annotation_route的複雜和全面,簡單實現路由url的匹配、參數解析賦值並返回page實例。
easy_router源碼戳我
使用@EasyRoute來註解須要加入Router的page, url做爲page的惟一標識,例如
@EasyRoute(url: "easy://flutter/pagea") class PageA extends StatefulWidget { final EasyRouteOption routeOption; PageA(this.routeOption); @override _PageAState createState() => _PageAState(); }
easy_router會調用page的構造函數並傳入EasyRouteOption參數,因此每一個page都應該有一個這樣的構造函數,若是url有參數,參數會放到EasyRouteOption對象的params屬性中,以便page獲取。
使用@easyRouter來註解你的router, 這樣就會生成router相關的內部邏輯, 例如
import 'package:example/route.router.internal.dart'; import 'package:easy_router/route.dart'; @easyRouter class Router { EasyRouterInternal internalImpl = EasyRouterInternalImpl(); dynamic getPage(String url) { EasyRouteResult result = internalImpl.router(url); if(result.state == EasyRouterResultState.NOT_FOUND) { print("Router error: page not found"); return null; } return result.widget; } }
EasyRouterInternalImpl就是最終生成的router實現, 執行命令生成EasyRouterInternalImpl實現
flutter packages pub run build_runner build --delete-conflicting-outputs
調用router打開url對應的page
MaterialButton( child: Text('ToPageA'), onPressed: (){ Navigator.of(context).push( MaterialPageRoute( builder: (context) { return Router().getPage('easy://flutter/pagea?parama=a'); } ) ); }, ),
感興趣本身改改,詳細使用參看源碼example
routeParseBuilder:負責解析@EasyRoute註解的page頁面,完成page和url的映射關係routerBuilder:讀取routeParseBuilder生成的映射,完成對EasyRouterInternalImpl寫入,依賴mustache4dart庫完成替換寫入