背景
現在常規的專案基本都有 2 個及以上的環境。為區分這些環境就需要對應的配置檔案,我們需要它們來控制 API 地址、應用名稱、版本號等資訊。
在 Flutter 1.17 中,Flutter 會將在編譯前將 dart-defines 傳遞給 iOS、Android、macOS。
因此,可以幾乎在各個時刻呼叫預先定義的 dart-defines。
目標
- 一處配置,多端使用。
- 熟悉的配置模式。採用
.env.*
模式來提供配置。 - 不入侵專案程式碼。
- 沒有第三方依賴。
來!開始倒騰吧!
先來看看 --dart-define
的官方說明
dart-define=<foo=bar>
Additional key-value pairs that will be available as constants from the String.fromEnvironment, bool.fromEnvironment, int.fromEnvironment, and double.fromEnvironment constructors.
利用 --dart-define
可以預先定義常量
flutter run --dart-define=APP_NAME=example_app --dart-define=USER_NAME=WzDTj
配置太多怎麼辦?
配置太多當然就寫到配置檔案中去呀!
// .env.local
APP_NAME=example_app_local
USER_NAME=WzDTj
// .env.staging
APP_NAME=example_app_staging
USER_NAME=WzDTj
// .env.production
APP_NAME=example_app_production
USER_NAME=WzDTj
可以指定配置檔案嗎?
寫好配置檔案,自然希望可以在命令中指定該檔案。
遺憾的是官方並沒有提供相關方法。
Github 上有人提議希望支援 .env,可惜官方並不願意。
既然官方不支援,那就自己寫個指令碼實現唄。
// flutter_tool.sh
#!/bin/bash
for i in "$@"
do
case $i in
--model=*)
MODEL="${i#*=}"
;;
*)
# unknown option
;;
esac
done
VARS=( $(cut -d ' ' -f1 .env.$MODEL) )
DART_DEFINES=""
for (( i = 0; i < ${#VARS[@]}; i++ )); do
DART_DEFINES+=" --dart-define=${VARS[i]}"
done
flutter $1 $DART_DEFINES
執行指令碼
./flutter_tool run --model=local
如何使用定義好的 dart-define
呢?
在專案程式碼中直接呼叫 fromEnvironment
方法。
// *.dart
const appName = String.fromEnvironment("APP_NAME", defaultValue: "default ppp name");
類似的還有:
- String.fromEnvironment
- bool.fromEnvironment
- int.fromEnvironment
怎麼控制 iOS 專案配置的資訊呢?
當我們用 Flutter 編譯 iOS 端時,會在 ./ios/Flutter
目錄下生成兩個檔案 Generated.xcconfig
、flutter_export_environment.sh
。
其中我們會看到之前設定的 dart-define。
// Generated.xcconfig
// ...
DART_DEFINES=APP_NAME%3Dexample_app_local,USER_NAME%3DWzDTj
// ...
// flutter_export_environment.sh
// ...
export "DART_DEFINES=APP_NAME%3Dexample_app_local,USER_NAME%3DWzDTj"
// ...
先來說下思路。
我們將 DART_DEFINES
解析到某個 .xcconfig
中,再通過引入該檔案來達到我們想要的目的。
首先需配置 iOS 專案的 Scheme
將以下指令碼程式碼輸入到 Build -> Pre-actions
。
function urldecode() { : "${*//+/ }"; echo "${_//%/\\x}"; }
IFS=',' read -r -a define_items <<< "$DART_DEFINES"
for index in "${!define_items[@]}"
do
define_items[$index]=$(urldecode "${define_items[$index]}");
done
printf "%s\n" "${define_items[@]}" > ${SRCROOT}/Flutter/DartDefines.xcconfig
這樣就可以在編譯前得到一份 DartDefines.xcconfig
檔案。
然後,在 Debug.xcconfig
、Release.xcconfig
中引入該檔案。
// ./ios/Flutter/Debug.xcconfig
#include "Generated.xcconfig"
#include "DartDefines.xcconfig"
// ./ios/Flutter/Release.xcconfig
#include "Generated.xcconfig"
#include "DartDefines.xcconfig"
最後,按需修改下 Info.plist
就行了。
例如:控制應用名稱。
// ./ios/Runner/Info.plist
// ...
<key>CFBundleName</key>
<string>$(APP_NAME)</string>
// ...
怎麼控制 Android 專案配置的資訊呢?
Flutter 會將所有的 dart-define
用逗號連結成一個字串傳遞給 Gradle。
// ./android/app/build.gradle
println(project.property('dart-defines'));
// Flutter v1.17 Output: APP_NAME=example_app_local,USER_NAME=WzDTj
// Flutter v1.20 Output: APP_NAME%3Dexample_app_local,USER_NAME%3DWzDTj
// 解析該字串
def dartDefines = []
if (project.hasProperty('dart-defines')) {
dartDefines = project.property('dart-defines')
.split(',')
.collectEntries { entry ->
def pair = URLDecoder.decode(entry, StandardCharsets.UTF_8.toString()).split('=')
[(pair.first()): pair.last()]
}
}
得到 dartDefines
後,便可按需配置了。
例如:定義一個字串資源供 AndroidManifest.xml
呼叫。
// ./android/app/build.gradle
defaultConfig {
// ...
resValue "string", "app_name", dartDefines.APP_NAME
}
// ./android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.flutter_app">
<application
android:name="io.flutter.app.FlutterApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher">
// ...
</application>
</manifest>
用 Android Studio 開發怎麼辦呢?
可以將上述指令碼中 DART_DEFINES
變數列印出來,填入 Run/Debug Configurations
中的 Additional arguments
即可。
參考資料
- [flutter_tools] Allow providing dart-defines to Android, iOS, macOS builds
- [flutter_tools] Allow to define –dart-define from system environment variables or from .env
- Flutter 1.17 — no more Flavors, no more iOS Schemas. Command argument that changes everything
- Flutter 1.20 — What you should know before you upgrade your project that uses compile-time variables
本作品採用《CC 協議》,轉載必須註明作者和本文連結