Set up multiple Firebase environments in Flutter

Tharuja Sandeepanie
8 min readApr 10, 2022

When building a mobile app, it is a good practice to configure our application for different development environments or release types. Most developers use separate environments such as,

  • Dev environment — for development purposes.
  • QA environment — for testing purposes.
  • Production environment — for end-users.

If you’re using Firebase in your Flutter project, we need to separate the Firebase project for each environment so that when we work with our environments, the relevant Firebase project must be connected to the corresponding environment.

Creating Flavors for Flutter

In Flutter, configuring different environments can be achieved through Flutter-flavors. Here, ‘flavor’ means ‘version’. We create multiple flavors in our mobile app, meaning that we build different versions of our app with different configuration options.

Through this article, I will explain both Android and iOS configurations of using these flavors. Before starting, let’s assume we need to configure 2 environments as prod and dev and our application Ids (bundle Id for iOS) for both environments are as follows:

  • prod environment: “com.example.tools.prod”
  • dev environment: “com.example.tools.dev”

Step 1 - Firebase Configuration

Create applications for iOS and Android in the Firebase console and download the google-services.json and GoogleService-Info.plist files for each Firebase project. Don’t forget to use the package names as defined above.

Step 2- Set up Android configuration

Android has a very straightforward method since flavors are already defined in their framework as productFlavors.

1- Open android/app/build.gradle file. Here inside the android block, add the below code. It indicates all the flavors (environments) you need to have and you can specify any extra options such as applicationId, applicationIdSuffix, versionName, versionNameSuffix, and versionCode in each flavor.

If you didn’t provide those options, that flavor considers the properties in defaultConfig.

flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationId "com.example.tools.dev"
}
prod {
dimension "env"
applicationId "com.example.tools.prod"
}
}

All flavors must be assigned to a named flavor dimension, which is a group of product flavors. If a given module specifies only one flavor dimension, the Android Gradle plugin automatically assigns all of the module’s flavors to that one dimension.

In Android, it creates build variants based on our build types and product flavors, and names them according to <product-flavor><Build-Type>. For example, now we created "dev" and "prod" product flavors, and in Flutter, we have "debug" and "release" build types. So, Gradle creates the following build variants. We can customise each build variant with its own features and resources.

  • devDebug
  • devRelease
  • prodDebug
  • prodRelease

2- In android/app/src/ folder create new folders as prod and dev as shown below. This folder name must match the flavor name specified in productFlavors. Put the downloaded google-services.json files into the correct folders considering the relevant environment. So, this will automatically connect to the correct firebase project according to the flavor we are running on.

android/
app/
src/
dev/ google-services.json
prod/ google-services.json

That’s all for the Android configuration. Now you can run your app using:

flutter run --flavor <flavor-name>

Step 3- Set up iOS configuration

For iOs configuration, we need to use Xcode Schemes which is same as product flavors in Android. Open ios/Runner.xcworkspace from Xcode and click on the Runner scheme as shown below.

1 - Here, go to “New Scheme” and create a new scheme as “dev”. Give Runner as the target.

2 - Next, go to “Manage Scheme” and rename the default scheme — “Runner” as prod.

Run the project with --flvor dev argument. You may notice it will give an error saying that Flutter needs a build configuration named as Debug-dev or similar. So, if we need to use the dev flavor(scheme) , we must create new build configurations named as Debug-dev for Debug mode , Release-dev for Release mode and Profile-dev for Profile mode.

3 - Select Runner project ->Info -> Configurations. Click on the plus icon and give “Duplicate Debug Configuration”. Then, rename the duplicated configuration as Debug-dev. Do the same process for duplicated release and profile configurations too.

4 - Then, rename the default build configurations as below in order to make them run in the prod environment.

  • Debug as Debug-prod
  • Release as Release-prod
  • Profile as Profile-prod

Alright !! :D Now we have 2 schemes as prod and dev. And we have 6 build configurations as

  • 1- Debug-prod. 2- Debug-dev
  • 3- Release-prod. 4- Release-dev
  • 4- Profile-prod 5- Profile-dev

5 - Now, we need to connect each build configuration to the correct scheme. First, select dev scheme and go to “Edit Scheme”.

In below window, Select “Run” and give “Debug-dev” as the build configuration. Give the correct build configurations in “Test”, “Profile”, Analyze” and “Archive” actions too as shown in the following image. Do this same process for prod scheme.

Alright !! :D Now we have connected all the actions in our 2 schemes to the correct build configuration.

6 - Next, we are going to change the bundle identifier for each scheme. Select “Runner” Target. Go to Build Settings -> Packaging -> Product Bundle Identifier. Here, you can give a bundle identifier for each build configuration. For build configurations in prod scheme we used -“com.example.tools.prod” and for build configurations in dev scheme we used-“com.example.tools.dev”.

7 - Now we need to connect our 2 schemes to relevant Firebase projects. For that, we create a new folder named “config” separately and inside it, we create two new folders as prod and dev. Put the downloaded GoogleService-Info.plist files into the correct folder considering the environment.

Then drag and drop this config folder explicitly into XCode inside to “Runner”.

ios/
config/
dev/ GoogleService-Info.plist
prod/ GoogleService-Info.plist

Next, we are going to instruct the build process to copy the correct GoogleServices-Info.plist file into the real location where Firebase init code expects to find GoogleServices-Info.plist file. For that, select “Runner” Target. Go to “Build Phases” , click on plus icon and give “New Run Script Phase”.

Then a new step named as “Run Script” will be added at the bottom. Rename it to a suitable name and drag it to be between “Run Script” and “Compile Sources” steps.

Expand our new step, there we have to add a script which instructs to copy the correct GoogleServices-Info.plist file into the correct location considering the environment (scheme) we are running on.

The script is as follows:


# Get a reference to the destination location for the GoogleService-Info.plist
# This is the default location where Firebase init code expects to find GoogleServices-Info.plist file.
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app# We have named our Build Configurations as Debug-dev, Debug-prod etc.
# Here, dev and prod are the scheme names. This kind of naming is required by Flutter for flavors to work.
# We are using the $CONFIGURATION variable available in the XCode build environment to get the build configuration.
if [ "${CONFIGURATION}" == "Debug-prod" ] || [ "${CONFIGURATION}" == "Release-prod" ] || [ "${CONFIGURATION}" == "Profile-prod" ] || [ "${CONFIGURATION}" == "Release" ]; then
cp "${PROJECT_DIR}/config/prod/GoogleService-Info.plist" "${PLIST_DESTINATION}/GoogleService-Info.plist"
echo "Production plist copied"elif [ "${CONFIGURATION}" == "Debug-dev" ] || [ "${CONFIGURATION}" == "Release-dev" ] || [ "${CONFIGURATION}" == "Profile-dev" ] || [ "${CONFIGURATION}" == "Debug" ]; then
cp "${PROJECT_DIR}/config/dev/GoogleService-Info.plist" "${PLIST_DESTINATION}/GoogleService-Info.plist"
echo "Development plist copied"fi

8 - As the last step, in the “Build Phases” -> “Copy Bundle Resources” phase, we need to delete the resource - “GoogleServices-Info.plist in Runner” in order to avoid copying of such file during the build time. We really don’t need to copy it since we are now instructing to copy the correct GoogleServices-Info.plist file from our “config” folder (step-7). I got it from here..Thanks !!

9 - Additional step - If you are using google sign in with firebase authentication, you need to copy the REVERSED_CLIENT_ID from GoogleServices-Info.plist file into the Info.plist file. Since, now we are using different firebase environments, we need to specify a variable in Info.plist and get the correct REVERSED_CLIENT_ID considering the environment we are running on. For that, I used this method..Thanks for that answer..!!

A) In ios/Flutter/Debug.xcconfig and ios/Flutter/Release.xcconfig files, create a user-defined variable by adding this code line at the bottom.

GOOGLE_SERVICE_REVERSED_CLIENT_ID = {Your REVERSED_CLIENT_ID found in GoogleService-Info.plist file}

B) In ios/Runner/info.plist file, replace the hard-coded REVERSED_CLIENT_ID with $(GOOGLE_SERVICE_REVERSED_CLIENT_ID) variable as below.

<key>CFBundleURLSchemes</key>
<array>
<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID --><string>$(GOOGLE_SERVICE_REVERSED_CLIENT_ID)</string>
</array>

C) Open Xcode and select “Runner” target. Then go to Build Settings -> User-Defined section. Here, you may find that, the variable which you have defined earlier as GOOGLE_SERVICE_REVERSED_CLIENT_ID has been added into this section. Now, you can set the actual environment specific REVERSED_CLIENT_ID for each build type.

Debug-dev = com.googleusercontent.apps.{dev client-id}
Debug-prod = com.googleusercontent.apps.{prod client-id}
Profile-dev = com.googleusercontent.apps.{dev client-id}
Profile-prod = com.googleusercontent.apps.{prod client-id}
Release-dev = com.googleusercontent.apps.{dev client-id}
Release-prod = com.googleusercontent.apps.{prod client-id}

--

--