How to support Multiple environments in Flutterflow Staging / Live and many more ?
Project set up I have are as follows
FlutterFlow Android App -
Firebase as backend for Android App
Same approach can be used for iOS app as well.
Client changes you need :
Default branch:
Create two firebase backend projects Development and Release , make your flutterflow project point to develop. Most of development will happen in dev version of firebase project , the release one is only for testing
Eg:
Add both firebase google-services-<Env> .json files to your android app
"./android/app/google-services-prod.json",
"./android/app/google-services-staging.json",
Preserve build.gradle and few other files ( Build.gradle will have signin configs and list of flavours supported )
Here is script that copies the content from flutterflow branch to Release branch while preserving the extra files we have added.
Flutterflow --- Default push will overwrite the flutterflow branch
Release branch - Release in same Flutterflow client repo.
I have created a shell script to copy the content preserving some of the files that need to be preserved
=============================================================
#!/bin/bash
# Ensure the script exits on any error
set -e
# Fetch the latest changes from all branches to ensure the local repo is up-to-date
git fetch --all
# Checkout the remote flutterflow branch into a local branch
git checkout -B flutterflow origin/flutterflow
# Checkout the remote Release branch into a local branch
git checkout -B Release origin/Release
# Pull the latest changes from the remote Release branch, rebase to handle conflicts
git pull --rebase origin Release
# Perform a hard reset on Release to make it exactly like flutterflow, discarding local Release's divergent changes
git reset --hard flutterflow
# Checkout specific files from the Release branch's previous state to preserve them
declare -a files_to_preserve=(
"android/app/google-services-prod.json"
"android/app/google-services-staging.json"
"ios/Runner/GoogleService-Info-staging.plist"
"ios/Runner/GoogleService-Info-prod.plist"
"build-script.sh"
"copyFromFlutterFlow.sh"
".gitignore"
"README.md"
"build-script.ps1"
"copyFromFlutterFlow.ps1"
"android/key.properties"
"android/app/build.gradle"
)
# Loop through the files and checkout each one from the old Release state (before reset)
for file in "${files_to_preserve[@]}"; do
git checkout HEAD@{1} -- "$file"
done
# Add all changes to stage the results of the reset and the checkout
git add .
# Commit the changes
git commit -m "Updated Release with flutterflow changes, preserving specific configurations and scripts"
# Try to push, if fails, force push
if ! git push origin Release; then
echo "Standard push failed; attempting to force push..."
git push origin Release --force
fi
echo "Release branch successfully updated with flutterflow, with specific files preserved."
echo "Release branch successfully updated with flutterflow, with specific files preserved."
====================================================================
Next you need build script to generate APK , before doing a build you need to make sure you copy the right google services file to generate the correct flavour of apk
===============
#!/bin/bash
# Function to get the app name from pubspec.yaml
get_app_name() {
local PUBSPEC_FILE="pubspec.yaml"
local APP_NAME=$(sed -n 's/^ *name: *//p' $PUBSPEC_FILE | tr -d '[:space:]')
echo "$APP_NAME"
}
# Function to copy Firebase configuration files based on flavor
copy_firebase_configs() {
local FLAVOR=$1
local ENVIRONMENT=$2
if [ "$FLAVOR" = "live" ]; then
echo "Copying production Firebase configuration files..."
cp android/app/google-services-prod.json android/app/google-services.json
cp ios/Runner/GoogleService-Info-prod.plist ios/Runner/GoogleService-Info.plist
elif [ "$FLAVOR" = "staging" ]; then
echo "Copying staging Firebase configuration files..."
cp android/app/google-services-staging.json android/app/google-services.json
cp ios/Runner/GoogleService-Info-staging.plist ios/Runner/GoogleService-Info.plist
else
echo "Unsupported flavor: $FLAVOR"
exit 1
fi
}
# Function to build Android debug APK
build_android_debug() {
echo "Building Android Debug APK..."
flutter clean
flutter build apk --debug
local APP_NAME=$(get_app_name)
mv build/app/outputs/flutter-apk/app-debug.apk "$APP_NAME-$FLAVOR-debug.apk"
}
# Function to build Android release APK
build_android_release() {
echo "Building Android Release APK/AAB..."
flutter clean
if [ "$AAB" = true ]; then
flutter build appbundle --release
local APP_NAME=$(get_app_name)
mv build/app/outputs/bundle/release/app-release.aab "$APP_NAME-$FLAVOR-release.aab"
else
flutter build apk --release
local APP_NAME=$(get_app_name)
mv build/app/outputs/flutter-apk/app-release.apk "$APP_NAME-$FLAVOR-release.apk"
fi
}
# Function to build iOS debug IPA
build_ios_debug() {
echo "Building iOS Debug IPA..."
flutter build ios --debug --no-codesign
# Add renaming logic if needed
}
# Function to build iOS release IPA
build_ios_release() {
echo "Building iOS Release IPA..."
flutter clean
flutter build ios --release --no-codesign
# Add renaming logic if needed
}
# Main script
if [ $# -lt 3 ]; then
echo "Usage: $0 <build_type> <platform> <flavor> [aab]"
echo "build_type should be 'debug' or 'release'"
echo "platform should be 'android' or 'ios'"
echo "flavor should be 'live' or 'staging'"
echo "Optional: aab to build Android App Bundle (AAB) instead of APK"
exit 1
fi
BUILD_TYPE=$1
PLATFORM=$2
FLAVOR=$3
AAB=false
# Check if the last argument is "aab"
if [ "$4" = "aab" ]; then
AAB=true
fi
# Copy Firebase configuration files based on flavor
copy_firebase_configs $FLAVOR
# Build Android or iOS binaries based on build type
if [ "$PLATFORM" = "android" ]; then
if [ "$BUILD_TYPE" = "debug" ]; then
build_android_debug
elif [ "$BUILD_TYPE" = "release" ]; then
build_android_release
fi
elif [ "$PLATFORM" = "ios" ]; then
if [ "$BUILD_TYPE" = "debug" ]; then
build_ios_debug
elif [ "$BUILD_TYPE" = "release" ]; then
build_ios_release
fi
else
echo "Invalid platform. It should be 'android' or 'ios'."
exit 1
fi
echo "Build completed!"
=========================================================
Build script can be invoked by this script in commandline
./build-script.sh release android live
@@@@@@@@@@@@@@@@@ Firebase functions @@@@@@@@@@@@@@@@@@@
Firebase functions - The idea here is to have one common code base with two different end points for these functions for development and Production
Add two service_account.key files , one for staging and other for prod in the same functions folder, please note these service account key are the ones through which you can access the respective Firebase project ( Admin SDK used these keys to access the files)
.env.falvour file can be added in functions folder , that can have several key value pairs
.env.staging
.env.prod
The content of these files are as follows (Development one is given here)
FLAVOUR=development
SUPRESSAUTH=false
Firebase functions can deployed by pointing to different firebase projects at deployment time
package.json in function should have these few lines
"deployfunctions:dev": "firebase deploy --only functions --project <Dev-FIREBASE-PROJECT>",
"deployfunctions:prod": "firebase deploy --only functions --project <PROD-FIREBASE-PROJECT>",
"deployhosting:dev": "firebase deploy --only hosting --project coachprodev",
"deployhosting:prod": "firebase deploy --only hosting --project coach-pro-mobile",
"predeploy:dev": "firebase use XXXXDevProjectXXX",
"predeploy:prod": "firebase use XXXX-prod-projectXXXX"
In your funtion init in backend you can read these values
const flavour = process.env.FLAVOUR || 'development';
admin.initializeApp({
credential: admin.credential.cert(<Please input appropriate service account file>),
databaseURL: flavour === 'production' ? <Ur Firestore url> : < Ur firestore development url>,
storageBucket: flavour === 'production' ? <Prod storage bucket for firebase storage> : <Development storage bucket>,
});
You can now use the flavour to develop custom logic.
BTW - one last thing in Flutter flow use group API calls and retrieve the base url for develop and prod from the Firebase remote config - this way there is no need to change single line of code in Flutterflow
The trick is to use the API base url from App state , populate app state with
${FFAppState().apiBaseUrl}
Hope this helps , drop email to [email protected] in case you need further 1:1 help. Above code is self explanatory - In this way you can have reliable client and server end points support for flutterflow