Flutter Assets 生成工具

4,137 阅读2分钟

前言

相信大家开始接触 Flutter , 准备给 Flutter 添加一张图片,满心欢喜运行起来,常常会收到劝退通知。

Exception has occurred.
FlutterError (Unable to load asset: assets/images/flutter_candies.png)

总结下❌原因,不外乎下面几点:

  • 忘记在 pubspec.yaml 中定义
  • pubspec.yaml 里面缩进问题
  • /\ 傻傻分不清楚
  • 写错字符串
  • 在模块中,忘记添加 package 参数

我的建议是新手上来先好好看看 文档 ,再写代码。

This is for you

那么就没有一种安全快速的方式吗?其实 pub 上面已经有很多的 工具 , 其中 低调fgen 是小伙伴们经常用的,评价最高的,也是最接近我需求的。

那么我为什么要自己做呢?

  • 我之前已经做过一个 assets 相关的工具,不想半途而废。
  • 希望能全自动,加入资源文件之后,就自动生成 pubspec.yaml 里面的定义以及资源文件字符串的 const 。
  • const 命名,我不太习惯看全大写,所以我设计三种方式。
     lowercase_with_underscores : "assets_images_xxx_jpg" 
     uppercase_with_underscores : "ASSETS_IMAGES_XXX_JPG" 
     lowerCamelCase             : "assetsImagesXxxJpg" 
  • 对于在模块里面的资源文件,使用的时候必须指定 package,所以我也贴心地生成了 package 对应的 const 。
class Assets {
  Assets._();
  static const String package = 'module_a';
  static const String assets_xxx_txt = 'assets/xxx.txt';
  static const String assets_images_xxx_jpg = 'assets/images/xxx.jpg';
  static const String assets_images_test_txt = 'assets/images/test.txt';
}

使用的时候这样

    Image.asset(
      Assets.assets_images_xxx_jpg,
      package: Assets.package,
    );
  • 学(bai)习(piao) fgen 之后,感觉自己更强大了,就剩下把自己的想法转换成代码了。

总的来说吧,一句话

1024, This is for you.

写在 1024,希望每个打工人都能更轻松地编程。

使用

环境准备

把 pub bin 的路径放到你的系统路径中。

PlatformCache location
macOS or Linux$HOME/.pub-cache/bin
Windows*%APPDATA%\Pub\Cache\bin

pub global

激活 assets_generator

执行 pub global activate assets_generator

操作命令

帮助命令

agen -h

生成命令的例子

agen -t d -s -r lwu

全部命令

-h, --[no-]help     显示帮助信息
-p, --path          Flutter 项目的根路径
                    (默认 ".")
-f, --folder        assets 文件夹的名字
                    (默认 "assets")
-w, --[no-]watch    是否继续监听 assets 的变化
                    (默认 开启)
-t, --type          pubsepec.yaml 生成配置的类型
                    "d" 代表以文件夹方式生成 "- assets/images/" 
                    "f" 代表以文件方式生成   "- assets/images/xxx.jpg" 
                    (默认 "d")
-s, --[no-]save     是否保存命令到本地
                    如果执行 "agen" 不带任何命令,将优先使用本地的命令进行执行
-o, --out           const 类放置的位置
                    (默认放置在 "lib" 下面)
-r, --rule          consts 的名字的命名规范
                    "lwu"(小写带下划线) : "assets_images_xxx_jpg" 
                    "uwu"(大写带下划线) : "ASSETS_IMAGES_XXX_JPG" 
                    "lcc"(小驼峰)      : "assetsImagesXxxJpg" 
                    (默认 "lwu")
-c, --class         const 类的名字
                    (默认 "Assets")
    --const-ignore  使用正则表达式忽略一些const(不是全部const都希望生成)     

Dart

在单个项目中使用

    Image.asset(Assets.assets_images_xxx_jpg);

在模块中使用

    Image.asset(
      Assets.assets_images_xxx_jpg,
      package: Assets.package,
    );

课后小结

args

args 真的是一个好库。我之前写法法路由的时候,解析命令全靠自己写。低调告诉我这个库之后效率提高 200%

低调 : you know nothing, 法法。

预置命令常用的参数

Option newOption(
    //命令全称
    String name,
    //命令简称,必须只能一个字符长度
    String abbr,
    //帮助描述
    String help,
    //参数值的帮助描述
    String valueHelp,
    //默认值
    defaultsTo) {
    ...
}

常用添加预置命令的方法

  ///bool 类型的命令
  void addFlag(String name,
      {String abbr,
      String help,
      bool defaultsTo = false,
      bool negatable = true,
      void Function(bool) callback,
      bool hide = false}) 
      
  ///String 类型的命令    
  void addOption(String name,
      {String abbr,
      String help,
      String valueHelp,
      Iterable<String> allowed,
      Map<String, String> allowedHelp,
      String defaultsTo,
      Function callback,)
      
  ///Iterable<String> 类型的命令, 可以接收多个值        
  void addMultiOption(String name,
      {String abbr,
      String help,
      String valueHelp,
      Iterable<String> defaultsTo,
      bool splitCommas = true,
      bool hide = false})      

如何使用 args

void main(List<String> args) {
  final ArgParser parser = ArgParser();
  parser.addFlag('help', abbr: 'h', help: 'Help usage', defaultsTo: false);
  final ArgResults results = parser.parse(args);
  if (results.wasParsed('help')) {
    print(parser.usage);
    return;
  }

yaml

用来解析 pubspec.yaml ,然后重新生成对 assets 的定义。之前法法路由中,我也有用到。

这里要注意一个坑,就是就算它包含 flutter 的 flag,但是 flutter 下面没有任何定义的话 yaml['flutter'] 依然为空。

  String yamlString = yamlFile.readAsStringSync();
  final YamlMap yaml = loadYaml(yamlString) as YamlMap;

  if (yaml.containsKey('flutter')) {
    final YamlMap flutter = yaml['flutter'] as YamlMap;
    if (flutter != null && flutter.containsKey('assets')) {
      final YamlList assetsNode = flutter['assets'] as YamlList;
      if (assetsNode != null) {
        final int start =
            assetsNode.nodes.first.span.start.offset - '   - '.length;
        final int end = assetsNode.span.end.offset;
        yamlString = yamlString.replaceRange(
          start,
          end,
          newAssets,
        );
      }
      //Empty
      else {
        final int end = yamlString.lastIndexOf('assets:') + 'assets:'.length;
        yamlString = yamlString.replaceRange(end, end, '\n$newAssets');
      }
    } else if (flutter != null) {
      final int end = flutter.span.end.offset;
      yamlString =
          yamlString.replaceRange(end, end, '\n  assets:\n$newAssets');
    }
    //Empty
    else {
      final int end = yamlString.lastIndexOf('flutter:') + 'flutter:'.length;
      yamlString =
          yamlString.replaceRange(end, end, '\n  assets:\n$newAssets');
    }
  } else {
    final int end = yaml.span.end.offset;
    yamlString =
        yamlString.replaceRange(end, end, 'flutter:\n  assets:\n$newAssets');
  }
  yamlFile.writeAsStringSync(yamlString);

watcher

这个也是看 低调fgen 学(piao)来的,这也赋予了工具,监听文件夹变化的能力,每当我们新增一个资源文件的时候,就会触发,这样我们就可以重新生成。

我们可以根据不同的事件,给出不同的提示信息。

StreamSubscription<FileSystemEvent> _watch(FileSystemEntity file) {
  if (FileSystemEntity.isWatchSupported) {
    return file.watch().listen((FileSystemEvent data) {
      if (data.isDirectory) {
        final Directory directory = Directory(data.path);
        //empty directory
        if (directory.listSync().isEmpty) {
          if (data.type == FileSystemEvent.delete) {
            if (watchMap.containsKey(directory)) {
              watchMap[watchMap].cancel();
            }
            dirList.remove(directory);
          }
          return;
        }
        _watch(directory);
        dirList.add(directory);
      }
      String msg;
      switch (data.type) {
        case FileSystemEvent.create:
          msg = green.wrap('create');
          break;
        case FileSystemEvent.delete:
          msg = red.wrap('delete');
          break;
        case FileSystemEvent.move:
          msg = yellow.wrap('move');
          break;
        case FileSystemEvent.modify:
          break;
        case FileSystemEvent.all:
          msg = yellow.wrap('operate');
          break;
        default:
      }
      if (msg != null) {
        print('\n$msg ${data.path}.\n');
        assetsChanged?.call();
      }
    });
  }
  return null;
}

Watcher 我做了封装,拿去用吧 watcher.dart

结语

作为一个老的打工人,目前一共写了三个工具:

希望能够对大家有所帮助。

欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果QQ群:181398081

最最后放上Flutter Candies全家桶,真香。