利用 Dart-Define 实现 Flutter 多环境配置

背景

现在常规的项目基本都有 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.xcconfigflutter_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.xcconfigRelease.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 即可。

参考资料

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 10

感覺可以嘗試使用fvm

github.com/leoafarias/fvm

3年前 评论

我想问一下为啥我打包后 用这个设置的环境就不能用了

2年前 评论
WzDTj (楼主) 2年前

可以可以,但是我还是把环境写到代码里,因为有动态更换域名的需求

2年前 评论
lyydhy 2年前

配置太多怎么办?# 配置太多当然就写到配置文件中去呀!

楼主后边的配置文件是怎么创建的,在哪层目录下呀?

1年前 评论
WzDTj (楼主) 1年前

flutter run --dart-define=APP_NAME=example_app --dart-define=USER_NAME=WzDTj 跑完这个后,flutter_export_environment.sh和Generated.xcconfig文件里是DART_DEFINES=QVBQX05BTUU9ZXhhbXBsZV9hcHA=,VVNFUl9OQU1FPVd6RFRq,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ== 不是 export "DART_DEFINES=APP_NAME%3Dexample_app_local,USER_NAME%3DWzDTj" 是怎么回事

1年前 评论
WzDTj (楼主) 1年前

楼主有demo吗,我还是不太清楚.dev文件及目录这块,是在根目录下建立三个文件,分别是.env.local .env.staging .env.production ,然后把flutter_tool.sh 文件也放在根目录下,在终端执行命令./flutter_tool run --model=local 这样吗。我的.env.local文件是file类型不知道对不对。

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!