flutter工程化之动态配置
动机
很多公司在实际开发中都有不少动态化配置的需求,比如说要根据不同app渠道加载不同的URL、secrets等配置项。这其中的实现方法也有很多,今天的应用案例则是--dart-define
和--dart-define-from-file
。
目标
- 一处配置多处使用
- 尽量少用第三方库
- 非入侵
用法
--dart-define
使用方法简单粗暴,直接把如下的命令添加上就好,无论是flutter build
还是flutter build
都可以:
--dart-define API_URL=api.openflutter.dev
如果有需要添加多个变量也是可以的:
--dart-define API_URL=api.openflutter.dev --dart-define MAP_KEY=ds5jkh2jjhjkljh
如果我有大量的变量怎么办?
--dart-define-from-file
我们也可以从文件中加载配置文件,文件格式可以是json
也可以是.env
:
--dart-define-from-file path/to/config.json
--dart-define-from-file path/to/.env
Flutter端获取变量
很简单:
final url = const String.fromEnvironment("API_URL");
final logEnabled = const bool.fromEnvironment("LOG_ENABLED");
final level = const bool.fromEnvironment("LEVEL");
补充一句:
千万别忘了写
const
。
如果你使用的.env
文件做为配置项,你也可以使用一个代码叫envied的代码生成工具,以简化相关操作。
但很多时候仅仅在Flutter端获取这些配置是不够的,我们也会需要在Android侧和iOS侧获取相关配置。
Android 端
找到app/build.gradle.kts
,旧的Flutter应该是app/build.gradle
,然后添加一些代码:
val dartDefines = mutableMapOf<String, String>()
if (project.hasProperty("dart-defines")) {
project.property("dart-defines").toString().split(",").forEach { entry ->
val decoded = String(Base64.getDecoder().decode(entry), Charsets.UTF_8)
val pair = decoded.split("=")
if (pair.size == 2) {
dartDefines[pair[0]] = pair[1]
}
}
}
defaultConfig {
applicationId = "com.example.flutter_dash_testing"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
dartDefines["API_URL"]?.let {
resValue("string", "api_url", it)
}
}
iOS侧
当我们用 Flutter 编译 iOS 端时,会在./ios/Flutter
目录下生成两个文件Generated.xcconfig
、flutter_export_environment.sh
。
在这两个文件中我们都会过来的dart-define
:
// flutter_export_environment.sh
export "DART_DEFINES=S0VZPWtleSBpcyB0ZXN0aW5n,QVBJX1VSTD1odHRwczovL2FwaS5leGFtcGxlLmNvbQ==,QVBQX05BTUU9SGVsbG8gV29ybGQ=,RkxVVFRFUl9BUFBfRkxBVk9SPXN0YWdpbmc="
// Generated.xcconfig
DART_DEFINES=S0VZPWtleSBpcyB0ZXN0aW5n,QVBJX1VSTD1odHRwczovL2FwaS5leGFtcGxlLmNvbQ==,QVBQX05BTUU9SGVsbG8gV29ybGQ=,RkxVVFRFUl9BUFBfRkxBVk9SPXN0YWdpbmc=
首先我们需要把这些经过base64编码过的变量解析到某个.xcconfig
中,然后再把该文件在Release.xcconfig
和Debug.xcconfig
引入,假设它的名字是GeneratedDartDefines.xcconfig
:
// ./ios/Flutter/Debug.xcconfig
#include "Generated.xcconfig"
#include "GeneratedDartDefines.xcconfig"
// ./ios/Flutter/Release.xcconfig
#include "Generated.xcconfig"
#include "GeneratedDartDefines.xcconfig"
假设我们要生成的文件名字为GeneratedDartDefines.xcconfig
。首先我们在./ios/Flutter
目录下生成一个空的GeneratedDartDefines.xcconfig
。然后打开Xcode,在Targets=>Runner=>Build Phases
选项卡中点击+号,选择New Run Script Phase
并把如下代码复制进去:
DART_DEFINES=$(cat "${SRCROOT}/Flutter/Generated.xcconfig" | grep "DART_DEFINES" | sed -E 's/DART_DEFINES=(.+)/\1/')
IFS=',' read -ra DEFINES <<< "$DART_DEFINES"
ALL_DECODED=""
for DEFINE in "${DEFINES[@]}"; do
# Decode Base64
DECODED=$(echo "$DEFINE" | base64 --decode)
echo "DECODED $DECODED"
# Skip Flutter internal variables
if [[ "$DECODED" != FLUTTER_* ]]; then
if [ -z "$ALL_DECODED" ]; then
ALL_DECODED="$DECODED"
else
# Use proper newline insertion
ALL_DECODED="${ALL_DECODED}"$'\n'"${DECODED}"
fi
fi
done
echo "$ALL_DECODED" > "${SRCROOT}/Flutter/GeneratedDartDefines.xcconfig"
最后,按需修改下Info.plist
即可,如修改app名字:
// ./ios/Runner/Info.plist
// ...
<key>CFBundleName</key>
<string>$(APP_NAME)</string>
// ...
开发具怎么配置
Android Studio
在绿色的运行按钮左侧有个下拉框,然后点击Edit Configurations
,其中有一个项目叫做Addtional run args
,把--dart-define-from-file .env
类似的参数添加进去就好了。当然你也可以选择保存配置,这样大家就可以共享这个快捷键了。
VSCode
改改launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "Flutter Dev",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"args": [
"--dart-define=API_URL=https://api.dev.example.com",
"--dart-define=APP_ENVIRONMENT=development",
"--flavor=dev"
]
},
{
"name": "Flutter Staging",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"args": [
"--dart-define-from-file=.env",
"--flavor=staging"
]
}
]
}