Folks I have set up different environments for Staging and Live with Flutterflow and Firebase both for client and server (Firebase Functions)

Success Stories

How to support Multiple environments in Flutterflow Staging / Live and many more ?

Project set up I have are as follows

  1. FlutterFlow Android App -

  2. Firebase as backend for Android App

  3. 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

7
6 replies