在移動開發(fā)工作中,我們?yōu)橐粋€項目至少需要配置兩個獨立的開發(fā)環(huán)境:“development開發(fā)環(huán)境” 和 “生產環(huán)境production ”?!∵@樣方便我們在開發(fā)環(huán)境里自由地開發(fā)和測試新的功能,而對生產環(huán)境的用戶不造成絲毫的影響。
以flutter 項目開發(fā)為例:
一、初始化項目
首先我們看入口文件
lib/main.dart
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: '我的項目',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: '我的主頁'),
);
}
}
然后,我們給項目建立一個新的主頁,注意這里和項目默認的模板有差異
lib/my_home_page.dart
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Build flavors'),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Every value is hardcoded in the app.
// No development or production variants exist yet.
new Text('This is the production app.'),
new Text('Backend API url is https://api.example.com/'),
],
),
),
);
}
}
二、把 app 區(qū)分不同的環(huán)境變量
我們舉例,以下的數據是需要和生產環(huán)境區(qū)分開來的
1. APP的標題:開發(fā)環(huán)境為“我的項目 DEV” 生產環(huán)境為“我的項目”
2. 第一個text widget:開發(fā)環(huán)境為“這是正在開發(fā)的APP” 生產環(huán)境為 “這是一個正式上線的APP”
3. API 接口調用:開發(fā)環(huán)境為 https://dev-api.example.com/ ,生產環(huán)境為https://api.example.com/ 。
三、創(chuàng)建配置信息對象
創(chuàng)建一個新文件用來保存所有環(huán)境特定配置信息
lib/app_config.dart
import 'package:meta/meta.dart';
class AppConfig {
AppConfig({
@required this.appName,
@required this.flavorName,
@required this.apiBaseUrl,
});
final String appName;
final String flavorName;
final String apiBaseUrl;
}
四、把 AppConfig 轉化為 InheritedWidget類
為了讓我們的AppConfig類成為InheritedWidget,我們將繼承InheritedWidget類,提供獲取實例的靜態(tài)方法“of”,并且重載了“updateShouldNotify”方法。
lib/app_config.dart
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
class AppConfig extends InheritedWidget {
AppConfig({
@required this.appName,
@required this.flavorName,
@required this.apiBaseUrl,
@required Widget child,
}) : super(child: child);
final String appName;
final String flavorName;
final String apiBaseUrl;
static AppConfig of(BuildContext context) {
return context.inheritFromWidgetOfExactType(AppConfig);
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
需要注意的是:
- 子構造函數的參數將成為我們的整個MaterialApp實例,我們用AppConfig對象包裝我們的應用程序。
- 我們創(chuàng)建了一個名為“of”的靜態(tài)方法。 這是InheritedWidgets的慣例。 它使我們能夠在需要時調用AppConfig.of(context)來獲取特定于環(huán)境的配置。
- 在updateShouldNotify方法中,我們只返回false。 這是因為我們的AppConfig在創(chuàng)建后不會改變。
接下來,我們?yōu)閮蓚€環(huán)境創(chuàng)建文件。
四.、為不同的環(huán)境創(chuàng)建不同的啟動文件
我們將為每個環(huán)境創(chuàng)建各自的“啟動文件”。 在我們的例子中,我們只有兩個環(huán)境,開發(fā)和生產,所以我們的文件將是main_dev.dart和main_prod.dart。 在每個文件中,我們將使用各自的配置數據創(chuàng)建一個AppConfig類的實例。 我們將MyApp的新實例傳遞給我們的AppConfig widget,以便我們應用中的任何widget都可以輕松獲取配置的實例。 然后,我們將調用runApp,它將成為我們整個應用程序的入口點。
lib/main_dev.dart
import 'package:build_flavors/app_config.dart';
import 'package:build_flavors/main.dart';
import 'package:flutter/material.dart';
void main() {
var configuredApp = new AppConfig(
appName: 'Build flavors DEV',
flavorName: 'development',
apiBaseUrl: 'https://dev-api.example.com/',
child: new MyApp(),
);
runApp(configuredApp);
}
生產應用程序啟動程序文件與開發(fā)文件完全相同,但具有不同的配置值。
lib/main.dart
import 'package:build_flavors/app_config.dart';
import 'package:build_flavors/my_home_page.dart';
import 'package:flutter/material.dart';
// We can remove this line here:
// void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Call AppConfig.of(context) anywhere to obtain the
// environment specific configuration
var config = AppConfig.of(context);
return new MaterialApp(
title: config.appName,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
在這里,我們只是獲取應用配置實例,并根據我們當前的環(huán)境正確設置我們的MaterialApp標題。 我們刪除了void main()=> runApp(new MyApp())行,因為我們的環(huán)境特定的啟動程序文件將覆蓋該行。
由于我們的整個應用程序都包裝在AppConfig widget中(它繼承了InheritedWidget 類),所以我們可以在任何地方通過調用AppConfig.of(context)來獲取的配置實例。
lib/my_home_page.dart
import 'package:build_flavors/app_config.dart';
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
return new Scaffold(
appBar: new AppBar(
title: new Text(config.appName),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text('This is the ${config.flavorName} app.'),
new Text('Backend API url is ${config.apiBaseUrl}'),
],
),
),
);
}
}
我們的主頁widget與之前基本相同,但具有特定環(huán)境的值。 像以前一樣,我們通過調用AppConfig.of(context)來獲得AppConfig對象的實例。
五、在不同的環(huán)境下運行APP
我們可以通過運行帶 --target 或者 -t 參數來運行不同的環(huán)境。
因此,在我們的例子中:
- 運行開發(fā)版本,采用:
flutter run -t lib / main_dev.dart
- 運行生產版本,采用
flutter run -t lib / main_prod.dart
要在Android上創(chuàng)建一個release版本,我們可以 運行
flutter build apk -t lib / main_ <environment> .dart
我們將為不同的環(huán)境生成對應的APK。 要在iOS上構建版本,只需將apk替換為ios。是不是很方便?
六、 在IntelliJ IDEA / Android Studio等ide中設置運行環(huán)境
如果您在Android Studio或IntelliJ IDEA的開發(fā)ide里與Flutter插件一起使用,可以輕松創(chuàng)建運行單獨環(huán)境所需的運行配置。 首先,點擊運行按鈕旁邊下拉菜單中的編輯配置。

然后,單擊+按鈕創(chuàng)建一個新的運行配置。 在列表中選擇Flutter

對于開發(fā)環(huán)境,輸入dev作為名稱。如果需要和你的小伙伴的版本控制中包含此運行配置,請確保選中共享復選框。 然后,為Dart入口點選擇lib / main_dev.dart文件。

下面這樣是不是很酷?:)

特別聲明:本文完全參考大牛# Iiro Krankka 的文章 《Separating build environments in Flutter apps》
完整的項目地址在 https://github.com/FlutterRocks/separating_build_environments