In Flutter development, particularly for larger apps, you often need different environments like development, staging, and production. Each environment requires its own Firebase project to ensure data isolation and prevent accidental deployment to the live environment. Setting up different Firebase projects for each flavor in Flutter involves configuring separate Firebase projects and ensuring that each build variant of your Flutter app connects to the correct Firebase project. Here’s a comprehensive guide.
Why Use Different Firebase Projects for Each Flavor?
- Data Isolation: Prevents mixing of data between development, staging, and production environments.
- Environment-Specific Configuration: Allows different configurations for each environment.
- Risk Mitigation: Avoids accidental deployments or modifications to the live production environment.
Step-by-Step Guide to Setting Up Different Firebase Projects for Each Flavor in Flutter
Step 1: Create Firebase Projects
First, you need to create separate Firebase projects in the Firebase Console for each environment (e.g., development, staging, and production).
- Go to Firebase Console: Navigate to Firebase Console.
- Add Project: Click on “Add project” and follow the steps to create a new project for each environment. Give each project a descriptive name (e.g., “MyApp – Development”, “MyApp – Staging”, “MyApp – Production”).
- Register Apps: For each project, register your Android and iOS apps, and download the
google-services.json(for Android) andGoogleService-Info.plist(for iOS) configuration files.
Step 2: Set Up Flutter Flavors
Flutter flavors allow you to create different build configurations of your app. You can define flavors in both Android and iOS projects.
Android Setup
- Modify
build.gradle: Openandroid/app/build.gradle. - Define
flavorDimensions: AddflavorDimensionsandproductFlavorsinside theandroidblock.
android {
// ...
flavorDimensions "environment"
productFlavors {
dev {
dimension "environment"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
}
staging {
dimension "environment"
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
}
prod {
dimension "environment"
}
}
// ...
}
- dev: For the development environment.
- staging: For the staging environment.
- prod: For the production environment.
iOS Setup
- Open Xcode: Open the iOS project in Xcode by navigating to
ios/Runner.xcworkspace. - Add Build Configurations: Go to “Product” -> “Scheme” -> “Edit Scheme…” and add new build configurations.
- Duplicate the “Debug” and “Release” configurations for each environment. Rename them to “Debug-dev”, “Release-dev”, “Debug-staging”, “Release-staging”, etc.
- Add User-Defined Settings:
- In the “Build Settings” tab, click on the “+” icon and select “Add User-Defined Setting”.
- Create a new setting named
PRODUCT_BUNDLE_IDENTIFIER. - Set the value for each configuration:
Debug-dev/Release-dev:com.example.myapp.devDebug-staging/Release-staging:com.example.myapp.stagingDebug/Release:com.example.myapp
- Modify Info.plist: Open
ios/Runner/Info.plistas source code. - Update Bundle Identifier: Add the following to reference the user-defined setting.
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
Step 3: Place Configuration Files
Now, place the Firebase configuration files (google-services.json and GoogleService-Info.plist) into their respective directories and rename them accordingly.
Android
- Create directories for each flavor in
android/app/src/:dev,staging, andprod. - Place the corresponding
google-services.jsonfile into each flavor directory. For example:android/app/src/dev/google-services.jsonandroid/app/src/staging/google-services.jsonandroid/app/src/prod/google-services.json(or keep it directly inandroid/app/)
- Update
android/app/build.gradleto apply thegoogle-servicesplugin to the correct source set.
apply plugin: 'com.google.gms.google-services'
iOS
- In Xcode, create a new folder for each environment (e.g., “FirebaseDev”, “FirebaseStaging”, “FirebaseProd”).
- Add the corresponding
GoogleService-Info.plistfile to each folder. - In Xcode, select the target “Runner” and go to “Build Phases” -> “Copy Bundle Resources”.
- Remove the existing
GoogleService-Info.plist. - Add the correct
GoogleService-Info.plistbased on the selected build configuration. This can be done by using build scripts.
Add a new Run Script phase in Build Phases. Ensure it is placed before the “Compile Sources” phase. Here’s the script:
# Set the firebase configuration file based on the build configuration
GOOGLE_SERVICE_INFO_PLIST="${PROJECT_DIR}/Runner/Firebase/GoogleService-Info.plist"
if [ "${CONFIGURATION}" == "Debug-dev" ] || [ "${CONFIGURATION}" == "Release-dev" ]; then
GOOGLE_SERVICE_INFO_PLIST="${PROJECT_DIR}/Runner/FirebaseDev/GoogleService-Info.plist"
elif [ "${CONFIGURATION}" == "Debug-staging" ] || [ "${CONFIGURATION}" == "Release-staging" ]; then
GOOGLE_SERVICE_INFO_PLIST="${PROJECT_DIR}/Runner/FirebaseStaging/GoogleService-Info.plist"
fi
# Copy the correct GoogleService-Info.plist to the build output directory
PLIST_DESTINATION="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app"
rm -f "${PLIST_DESTINATION}/GoogleService-Info.plist"
cp "${GOOGLE_SERVICE_INFO_PLIST}" "${PLIST_DESTINATION}/GoogleService-Info.plist"
Step 4: Configure Flutter to Use Flavors
To build your Flutter app with specific flavors, use the following commands:
Android
flutter build apk --flavor dev -t lib/main_dev.dart
flutter build apk --flavor staging -t lib/main_staging.dart
flutter build apk --flavor prod -t lib/main_prod.dart
iOS
flutter build ios --flavor dev -t lib/main_dev.dart --build-name 1.0.0-dev --build-number 1
flutter build ios --flavor staging -t lib/main_staging.dart --build-name 1.0.0-staging --build-number 1
flutter build ios --flavor prod -t lib/main_prod.dart --build-name 1.0.0 --build-number 1
Step 5: Create Separate Entry Points
For better code organization, create separate entry points for each flavor. This helps in setting up flavor-specific configurations before the app starts.
- Create
main_dev.dart,main_staging.dart, andmain_prod.dartfiles in thelib/directory. - In each file, configure the environment-specific settings.
// main_dev.dart
import 'package:flutter/material.dart';
import 'package:your_app/app.dart';
import 'package:your_app/config/environment.dart';
void main() {
Environment.init(flavor: Flavor.dev);
runApp(MyApp());
}
// main_staging.dart
import 'package:flutter/material.dart';
import 'package:your_app/app.dart';
import 'package:your_app/config/environment.dart';
void main() {
Environment.init(flavor: Flavor.staging);
runApp(MyApp());
}
// main_prod.dart
import 'package:flutter/material.dart';
import 'package:your_app/app.dart';
import 'package:your_app/config/environment.dart';
void main() {
Environment.init(flavor: Flavor.prod);
runApp(MyApp());
}
Define the Environment and Flavor classes:
// lib/config/environment.dart
enum Flavor {
dev,
staging,
prod
}
class Environment {
static Flavor? appFlavor;
static String get appName {
switch (appFlavor) {
case Flavor.dev:
return "MyApp Dev";
case Flavor.staging:
return "MyApp Staging";
case Flavor.prod:
return "MyApp";
default:
return "MyApp";
}
}
static void init({required Flavor flavor}) {
appFlavor = flavor;
}
}
Step 6: Access Firebase Services
To use Firebase services, initialize them as usual. The correct configuration files will be loaded based on the flavor.
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Firebase Flavors',
home: Scaffold(
appBar: AppBar(
title: Text('Firebase Flavors Example'),
),
body: Center(
child: Text('Running on ${Environment.appName}'),
),
),
);
}
}
Tips and Best Practices
- Use Environment Variables: For sensitive configuration, use environment variables instead of hardcoding values in your Dart code.
- Automate Builds: Integrate flavor configurations into your CI/CD pipeline for automated builds and deployments.
- Consistent Naming: Maintain consistent naming conventions for flavors, Firebase projects, and configuration files.
- Testing: Thoroughly test each flavor to ensure it connects to the correct Firebase project and functions as expected.
Conclusion
Setting up different Firebase projects for each flavor in Flutter involves a series of configurations in both Android and iOS projects. By following this step-by-step guide, you can ensure that each build variant of your Flutter app connects to the appropriate Firebase project, providing data isolation and environment-specific configurations. This setup is crucial for maintaining a robust and reliable development workflow, especially in large and complex applications.