搞定了安全、可拓展的 Flutter 环境Env配置!

539 阅读4分钟

1_NpRZ3lBwPoobnYKUgrfs0Q.webp

前言

本文为转载、翻译技术文档

原文链接为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

可以通过下图找到设置入口

1_42nCRUls2LjsPMIsL-4Xcw.webp

1_vsWhSr2aSEaAchdHIKEePg.webp

在 Additional run args:追加 --dart-define-from-file=.env/development.env

VSCode

VS Code 配置的运行文件地址为:PROJ_DIR/.vscode/launch.json

如果lanch.json不存在上述目录地址上,可以按照如下图创建一个

1_oneGrEP9e9ImTB-uSyvC0A.webp

选择 Dart & Flutter,VS Code会创建如下的lanch.json文件

1_EWbDqsFxNC338RjLDoDgPA.webp

需要注意profilerelease模式,不需要按此配置。因为我们通常运行APP在debug模式上。当然硬要使用也是可以。

现在我们需要添加配置到Flutter:Launcher

VS Code — Add Configuration

1_CT5PY_CZorq274rCEInRew.webp

1_3ueXjzMyweznm_dH0sdEgw.webp

现在我们可以添加如下的配置

"args": [  
"--dart-define-from-file",  
".env/development.env"  
]

1_TOxVE9A5Vg-0UAvDdF-MaA.webp

为开发工具配置完之后,接下来我们需要创建一个访问环境变量入口类

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

1_fN8cWbegWj0thoSD_K46ZA.webp

现在只需要声明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)  
}  
}