在 Day 12 完成 UI 測試架構後,我們要開始建立 CrewUp 的雲端基礎設施。今天我們要解決一個實際專案中很重要的問題:如何同時處理多環境管理和 Firebase 整合?
從專案開發的經驗來看,這兩個需求往往會一起出現:我們需要不同的開發環境來測試功能,同時也需要雲端服務來儲存資料。今天就來分享我們在 CrewUp 專案中的實際作法。
在 CrewUp 專案中,我們選擇 Firebase 的考量主要有這幾個面向:
零設定的開發體驗
即時功能很實用
成本控制比較容易
在專案初期,Firebase 的 Spark(免費)計畫提供了一些足以支撐 MVP 階段的免費額度,例如:
Firebase 特別適合我們的應用情境:
在開發 CrewUp 的過程中,我們發現需要同時維護幾個不同的版本:
如果只有一個版本,開發過程中可能會遇到這些情況:
在比較了幾種作法後,我們發現 Flutter Flavorizr 是不錯的選擇:
為什麼我們不選擇手動設定?
Flavorizr 有哪些好處?
在我們專案中,可以參考這樣的 pubspec.yaml
Flavorizr 設定:
# pubspec.yaml
flavorizr:
# 自動建立這些設定檔
instructions:
- assets:downloadIcons
- android:androidManifest
- android:buildGradle
- flutter:flavors
- flutter:app
- flutter:main
- ios:podfile
- ios:xcconfig
- ios:buildTargets
- ios:schema
- ios:plist
# 三個環境的設定
flavors:
# 開發環境:橘色圖示,方便辨識
development:
app:
name: "Crew Up Dev"
android:
applicationId: "com.example.crewup.develop"
ios:
bundleId: "com.example.crewup.develop"
displayName: "Crew Up Dev"
# 測試環境:黃色圖示,給測試人員使用
staging:
app:
name: "Crew Up Staging"
android:
applicationId: "com.example.crewup.staging"
ios:
bundleId: "com.example.crewup.staging"
displayName: "Crew Up Staging"
# 正式環境:藍色圖示,上架版本
production:
app:
name: "Crew Up"
android:
applicationId: "com.example.crewup"
ios:
bundleId: "com.example.crewup"
displayName: "Crew Up"
安裝和設定
# 1. 在 pubspec.yaml 中加入依賴
dev_dependencies:
flutter_flavorizr: ^2.4.1
# 2. 執行指令產生設定檔
flutter packages pub run flutter_flavorizr
# 3. 清理和重新建構
flutter clean && flutter pub get
執行後,Flavorizr 會自動產生:
lib/flavors.dart
:環境管理的程式碼lib/main_development.dart
、lib/main_staging.dart
、lib/main_production.dart
:不同環境的入口檔案(已實作)建構不同環境的 App
使用 --flavor
參數來指定環境:
# 建構開發版本
flutter build apk --flavor development -t lib/main_development.dart
# 建構測試版本
flutter build apk --flavor staging -t lib/main_staging.dart
# 建構正式版本
flutter build apk --flavor production -t lib/main_production.dart
# 直接執行特定環境(開發時很方便)
flutter run --flavor development -t lib/main_development.dart
實際使用的好處
在實際使用 Flavorizr 和 Firebase 的過程中,開發過程中可能會遇到一些問題,這裡分享一些我們用過的解決方法:
Flavorizr 執行失敗
# 問題:執行 flutter packages pub run flutter_flavorizr 時出現錯誤
# 解決方案:
# 1. 確保 Flutter 版本相容
flutter --version
# 2. 清理專案並重新安裝依賴
flutter clean && flutter pub get
# 3. 檢查 pubspec.yaml 格式是否正確
# 確保縮排使用空格,不要混用 Tab
Firebase 設定檔遺失或損壞
# 問題:google-services.json 或 GoogleService-Info.plist 遺失
# 解決方案:重新下載設定檔
flutterfire configure --project=your-project-id
# 或者手動從 Firebase Console 下載
# Android: Project Settings > Your apps > Download google-services.json
# iOS: Project Settings > Your apps > Download GoogleService-Info.plist
多環境切換注意事項
建構錯誤處理
# 問題:建構時找不到 flavor
# 解決方案:檢查建構指令是否正確
flutter build apk --flavor development -t lib/main_development.dart
# 問題:iOS 建構失敗
# 解決方案:清理 iOS 快取
cd ios && pod deintegrate && pod install
Firebase 初始化錯誤
// 問題:Firebase 初始化失敗
// 解決方案:檢查 firebase_options.dart 是否正確產生
// 確保 DefaultFirebaseOptions.currentPlatform 能正確識別平台
在我們專案中,目前有使用到這些 Firebase 服務:
目前已整合的服務:
開發中的服務:
從我們的開發經驗來看,建議為不同環境建立獨立的 Firebase 專案。這樣不僅能從源頭避免資料污染和安全風險,也比較符合團隊合作的工作流程。
1. 建立三個獨立的 Firebase 專案
前往 [Firebase Console] 建立三個專案:
為什麼要分開?
開發環境專案:
測試環境專案:
正式環境專案:
2. 使用 FlutterFire CLI 設定多專案
為每個環境設定對應的 Firebase 專案:
開發環境設定:
# 設定開發環境專案
flutterfire configure --project=example-crewup-dev --platforms=android,ios
測試環境設定:
# 設定測試環境專案
flutterfire configure --project=example-crewup-staging --platforms=android,ios
正式環境設定:
# 設定正式環境專案
flutterfire configure --project=example-crewup-prod --platforms=android,ios
通用設定指令:
# 安裝工具
npm install -g firebase-tools
dart pub global activate flutterfire_cli
# 登入 Firebase
firebase login
# 自動設定專案(選擇對應的專案)
flutterfire configure
這個指令會為每個環境產生對應的設定檔:
開發環境:
lib/firebase_options_dev.dart
google-services-dev.json
GoogleService-Info-dev.plist
測試環境:
lib/firebase_options_staging.dart
google-services-staging.json
GoogleService-Info-staging.plist
正式環境:
lib/firebase_options_prod.dart
google-services-prod.json
GoogleService-Info-prod.plist
通用設定檔:
lib/firebase_options.dart
google-services.json
GoogleService-Info.plist
現在我們有了三個獨立的 Firebase 專案,接下來要讓 Flavorizr 能夠根據不同環境自動載入對應的 Firebase 設定。
在我們專案中,lib/flavors.dart
檔案是 Flavorizr 自動產生的:
// lib/flavors.dart
// (imports omitted)
enum Flavor {
development,
staging,
production,
}
class F {
static Flavor? appFlavor;
static String get name => appFlavor?.name ?? '';
static String get title {
switch (appFlavor) {
case Flavor.development:
return 'Crew Up Dev';
case Flavor.staging:
return 'Crew Up Staging';
case Flavor.production:
return 'Crew Up';
default:
return 'title';
}
}
}
環境相關的設定
我們可以根據不同環境做不同的設定:
// lib/main_production.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 從 --flavor 參數取得環境設定
const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'production');
F.appFlavor = Flavor.values.firstWhere(
(element) => element.name == flavor,
orElse: () => Flavor.production,
);
// Initialize Firebase
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// Initialize Firebase Cloud Messaging
await FCMService.initialize();
// 其他初始化...
runApp(UncontrolledProviderScope(container: container, child: const App()));
}
經過這次的設定,我們的 CrewUp 專案目前具備了這些功能:
--flavor
參數自動切換)多環境管理很方便
Firebase 整合度很高
目前的實作狀況:
正在開發的功能:
明天,我們將深入探討 Firebase Authentication 的實際應用,學習如何處理使用者登入狀態管理,以及如何整合 Google 登入功能到我們的專案中。
希望這些經驗對大家有幫助,我們明天 Day 14 再繼續!