前言
本文为转载、翻译技术文档
原文链接为Set Up Environment Variables in Flutter for Secure and Scalable Apps | by Nayan Babariya | Medium
感谢原文作者@Nayan Babariya
环境变量常用于根据最终产品上运行的环境对项目进行不同的配置。
开发中的环境变量包括API端点、API密钥、支付密钥、功能切换、调试级别等。
列出一些在Flutter中使用他的好处:
- 分离:环境变量保存来自代码库的不同文件。因此,您的敏感密钥和数据不会硬编码在代码上,从而使代码更加干净和安全。
- 安全:可以避免在将环境变量文件提交到代码管理服务器上,保证一些敏感私钥的安全。
- 灵活性:不需要改动代码,即可改变环境变量的值。比如:你可以开发一个测试环境和正式环境,对所有的环境变量进行灵活管理
- 可拓展性:您可以随着应用程序的增长和需要轻松添加新的环境设置。不需要更改代码。
- 跨平台一致性:您只需要在一个地方声明不同平台的值,如iOS、Android、Web等。
- 易于测试:这使得在不同的环境中测试产品变得容易,只需要切换环境
- CI/CD集成:通过在不同的工作流程中添加环境变量来管理CI/CD管道非常简单。
- 应用白名单:可以通过为不同的客户端创建各种配置,来轻松对应用程序设置白名单
要在Flutter中创建环境变量设置,请按照以下简单步骤操作:
创建.env文件
我们需要为每个环境创建一个.env文件。我们正在开发和生产环境。虽然,你可以根据需要添加另一个。
development.env
# Used for REST API
REST_API_URL=https://dev-example.com/api/
REST_API_KEY=dev-ABCD1234
# Used for Google Maps.
GOOGLE_MAPS_API_KEY=gm-test-EFGH1234
production.env
# Used for REST API
REST_API_URL=https://prod-example.com/api/
REST_API_KEY=prod-PQRS5678
# Used for Google Maps.
GOOGLE_MAPS_API_KEY=gm-prod-TUVW5678
然后设置运行配置
Android Studio
Android Studio配置的运行文件地址为:PROJ_DIR/.idea/runConfigurations/**.xml
可以通过下图找到设置入口
在 Additional run args:追加 --dart-define-from-file=.env/development.env
VSCode
VS Code 配置的运行文件地址为:PROJ_DIR/.vscode/launch.json
如果lanch.json不存在上述目录地址上,可以按照如下图创建一个
选择 Dart & Flutter,VS Code会创建如下的lanch.json文件
需要注意profile和release模式,不需要按此配置。因为我们通常运行APP在debug模式上。当然硬要使用也是可以。
现在我们需要添加配置到Flutter:Launcher
现在我们可以添加如下的配置
"args": [
"--dart-define-from-file",
".env/development.env"
]
为开发工具配置完之后,接下来我们需要创建一个访问环境变量入口类
Flutter中可以通过Environment轻松访问环境变量
/// Class for Environment variables.
final class Environment {
static const restApiUrl = String.fromEnvironment('REST_API_URL');
static const restApiKey = String.fromEnvironment('REST_API_KEY');
static const gMapsKey = String.fromEnvironment('GOOGLE_MAPS_API_KEY');
}
在Android中,访问上述环境变量。
我们可以在Android的原生代码上,访问所有定义好的环境变量。 我们需要写下如下的代码,在build.gradle文件上
...
def dartDefines = [:]
if (project.hasProperty('dart-defines')) {
dartDefines = dartDefines + project.property('dart-defines')
.split(',')
.collectEntries { entry ->
def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
pair.length == 2 ? [(pair.first()): pair.last()] : [:]
}
}
android {
...
...
defaultConfig {
applicationId "com.example.env_test_example"
minSdkVersion 24
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
resValue "string", "google_maps_api_key", dartDefines.GOOGLE_MAPS_API_KEY
}
...
...
}
我们不需要任何文件中声明的hard code 的环境变量,只需要按照如下的定义
resValue "string", "google_maps_api_key", dartDefines.GOOGLE_MAPS_API_KEY
现在采用下方的写法,就可以轻松的访问环境变量
...
<application>
...
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_api_key" />
...
</application>
...
在IOS中,访问上述环境变量。
没有像安卓一样直接的方式访问到环境变量。所以我们需要自行编写一个脚本解析变量,当允许Flutter IOS APP时候
extract_dart_defines.sh PROJ_DIR/ios/Flutter/extract_dart_defines.sh
#!/bin/sh
# Specify the file path to write Dart define.
# Here we will create a file named `Dart-Defines.xcconfig`.
OUTPUT_FILE="${SRCROOT}/Flutter/Dart-Defines.xcconfig"
# Empty the file first so that no old properties remain when changing the contents of the Dart define.
: > $OUTPUT_FILE
# Decode Dart define with this function.
function decode_url() { echo "${*}" | base64 --decode; }
IFS=',' read -r -a define_items <<<"$DART_DEFINES"
for index in "${!define_items[@]}"
do
item=$(decode_url "${define_items[$index]}")
# Dart definition also includes items automatically defined on the Flutter side.
# However, if you write out those definitions, you will not be able to build due to an error, so
# Items starting with flutter or FLUTTER are not output.
lowercase_item=$(echo "$item" | tr '[:upper:]' '[:lower:]')
if [[ $lowercase_item != flutter* ]]; then
echo "$item" >> "$OUTPUT_FILE"
fi
done
运行以下命令,确保脚本文件具有读,写,执行等权限
chmod 755 ios/Flutter/extract_dart_defines.sh
现在我们只需在运行Flutter IOS APP时,执行上述.sh脚本。就会自动生成Dart-Defines.xcconfig文件。
为了实现此目标,我们需要在Xcode上添加 run script pre-action。操作为:XCode > Click on Runner > Edit Scheme > Build > Pre-actions > + > New Run Script Action
现在只需要声明google_maps_api_key在Info.plist上。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>GOOGLE_MAPS_API_KEY</key>
<string>$(GOOGLE_MAPS_API_KEY)</string>
...
</dict>
</plist>
如果我们是在swift上使用,则在Info.plist按下面配置
import UIKit
import Flutter
import GoogleMaps
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// This is the use of how we can access GOOGLE_MAPS_API_KEY from Info.plist
if let googleMapsApiKey = Bundle.main.object(forInfoDictionaryKey: "GOOGLE_MAPS_API_KEY") as? String {
GMSServices.provideAPIKey(googleMapsApiKey)
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}