Flutter系列:4.基於註解的代碼生成應用

前言

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能夠攔截註解獲取並解析上下文信息,經過解析註解實現source_gen的相關Generator就能夠動態的生成代碼了;api

source_gen是封裝自build和 analyzer,並在此基礎上提供友好的api封裝。build是一個提供構建控制的庫,analyzer是提供dart語法靜態分析功能的庫,source_gen將其整合即可以實現一套基於註解的代碼生成工具。app

clipboard.png

代碼生成

使用Annotation+source_gen的方式能夠便捷的生成代碼,source_gen經過攔截Annotation,解析其上下文element而後經過builder便可動態生成代碼,下面簡易的代碼生成Demo。ide

建立package

終端運行:函數

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工程來作示例

建立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

easy_router

目前在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庫完成替換寫入

相關文章
相關標籤/搜索