Flutter 小技巧之 3.7 更灵活的编译变量支持
今天我们聊个简单的知识点,在 Flutter 3.7 的 release-notes 里,有一个没有出现在 announcement 说明上的 change log ,可能对于 Flutter 团队来说这个功能并不是特别重要,但是对于我个人而言,这是一个十分重要的能力补充:
- [flutter_tools] Fix so that the value set by
--dart-define-from-file
can be passed to Gradle by @blendthink in https://github.com/flutter/flutter/pull/114297
翻到这个小功能,纯属是意外之喜。
Dart
在 3.7 版本之前,如果我们需要在编译时动态给 Flutter 添加变量信息,那么我们会用到 --dart-define
,例如:
flutter run --dart-define=APP_CHANNEL=Offical
const APP_CHANNEL = String.fromEnvironment('APP_CHANNEL');
我们可以通过 --dart-define
在命令行指定一个变量,然后在 Flutter 里通过 String.fromEnvironment
读取它,一般场景下它是满足需求的,但是:
如果当你需要定义多个变量时,命令就会变得冗长且不好维护
如果你是混合开发,变量还需要同步修改到原生项目的配置里,就会变得麻烦
在此之前,针对同步修改到不同原生项目的配置,我是通过自定义脚本去实现:
- Android 上利用 gradle 脚本,参考 RN 上的
dotenv
读取某个脚本配置,修改project.env
- iOS 上通过读取脚本配置,然后利用系统的
PlistBuddy
命令在编译时插入和修改某些参数
而现在,从 Flutter 3.7 开始,它变得更简单了,因为你可以使用 --dart-define-from-file
:
flutter run --dart-define-from-file=config.json
////// config.json //////
{
"TEST_KEY1": "test key 1",
"TEST_KEY2": "test key 2"
}
同样是 dart define ,但是 --dart-define-from-file
可以直接从一个 json 文件上读取配置,然后转成一个 Map
,之后配置到 Environment 里,同样是可以在 dart 里通过 String.fromEnvironment
去读取参数,而 json 文件的配置方式,可以让你在需要配置多个变量时参数管理变得更好维护。
那到这里就结束了吗?显然不是,前面我们说过同步修改到不同原生项目的配置,而 Flutter 3.7 下官方也正式支持。
Android
首先是 Android ,我们可以在 app/build.gradle
文件下定义一个 dartEnvVar
变量,它主要是用来读取前面 json 文件注入到 project
的参数。
然后我们就可以在 app/build.gradle
下直接通过 dartEnvVar
引用对应参数,比如定义 resValue
,可以看到 dartEnvVar
在编译时,成功读取到 json 文件里的参数。
如下图所示,能通过 project
读取 dart 的环境变量配置之后,我们就可以定义有 resValue
去修改 AndroidManifest
文件,甚至定义插入到 BuildConfig
里在原生代码引用,而对于配置我们只需要维护一份 json 文件即可。
那它是如何实现的?简单来说,在 flutter/packages/flutter_tools/lib/src/build_info.dart 脚本下,之前读取的 json 文件可以得到一个 dartDefineConfigJsonMap
对象,它会被转化为一个 Gradle 参数列表,在之后的 assembleTask
里被作为参数执行。
这里需要注意,定义的 key 不能和与定制的 key 冲突,比如
dart-obfuscation
等。
如下图所示,最终执行的时候就会是 -PTEST_KEY1=test key 1 -PTEST_KEY2=test key 2 这样的效果。
iOS
iOS 上同样也很简单,你只需要在 Info.plist
上定义好 key-value 的引用即可,因为 iOS 上在 --dart-define-from-file
编译时,同样会生成对应的 xcconfig
配置信息。
在 ios/Flutter
目录下,编译时会产生两个忽略文件,分别是 flutter_export_environment.sh
和 Generated.xcconfig
,可以看到编译后这两个文件下都产生了对应的 key-value 。
这里需要注意,在 iOS 上
xcconfig
格式会将//
读取为注释分隔符 ,也就是//
之后的内容会被忽略,也就是说,你不能通过它来传递 url ,比如https://xxxx
,因为//
后会被忽略。
当然,如果你需要默认值,那么你也可以在 ios/Flutter
目录下的 Debug.xcconfig
和 Release.xcconfig
上进行定制配置。
和 Android 一样, iOS 在编译时会对 --dart-define-from-file
的参数进行转化变成 xcconfig
参数,从而实现 dart 和 iOS 端公用一份变量配置的效果。
最后
可以看到 --dart-define-from-file
的使用和实现并不复杂,在没有它之前我们也可以通过一些手段来实现类似的效果。
但是 --dart-define-from-file
命令的出现简化了整个构建流程,让编译动态配置的链条变得更加灵活可靠,所以它无疑是 3.7 里最容易被忽略的实用更新。
不得不说,Flutter 3.7 给我们带来了不少的惊喜,例如 toImageSync 和 background isolate 都是期待已久的功能,而类似 --dart-define-from-file
的支持,也在不断完善 Flutter 的开发体验。
最后,从 3.7 开始的小版本更新有两个特征:
- 1、impeller 确实还有不少问题
- 2、impeller 真的来了,就算预览功能也要 fix 到稳定分支
期待下个版本 impeller 能给我们带来更好的体验。