flutterfire_cli icon indicating copy to clipboard operation
flutterfire_cli copied to clipboard

Support for multiple scheme dev, staging, prod

Open ghensto opened this issue 2 years ago • 238 comments

ghensto avatar Dec 11 '21 06:12 ghensto

Could you give some more details on what exactly it is you're looking for?

Ehesp avatar Dec 11 '21 09:12 Ehesp

I have the same requirement so I can comment on this.

My app is configured to use multiple flavors.

As such, I have three sets of package_names / bundle IDs:

  • Dev: com.codewithandrea.flutterfire-flavors.dev
  • Staging: com.codewithandrea.flutterfire-flavors.stg
  • Prod: com.codewithandrea.flutterfire-flavors

Here's how they look like on the Xcode build settings:

Screenshot 2021-12-13 at 09 40 02

And here's how they're set up inside android/app/build.gradle

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.codewithandrea.flutterfire_flavors"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
    
    ...
    
    flavorDimensions "default"
    productFlavors { 
        production {
            dimension "default"
            applicationIdSuffix ""
            manifestPlaceholders = [appName: "Flutterfire Flavors"]
        }
        staging {
            dimension "default"
            applicationIdSuffix ".stg"
            manifestPlaceholders = [appName: "[STG] Flutterfire Flavors"]
        }        
        development {
            dimension "default"
            applicationIdSuffix ".dev"
            manifestPlaceholders = [appName: "[DEV] Flutterfire Flavors"]
        }
    }    

Accordingly, I want to have a separate Firebase project for each flavor.

I tried to run flutterfire configure on my project, but by default it only works with one Firebase project and doesn't take into account the platform-specific configuration.

Here's a log:

✔ Select a Firebase project to configure your Flutter application with · <create a new project> 
✔ Enter a project id for your new Firebase project (e.g. my-cool-project) · flutterfire-flavors-dev 
i New Firebase project flutterfire-flavors-dev created succesfully.
✔ Which platforms should your configuration support (use arrow keys & space to select)? · android, ios, web, macos 
i Firebase android app com.codewithandrea.flutterfire_flavors is not registered on Firebase project flutterfire-flavors-dev.
i Registered a new Firebase android app on Firebase project flutterfire-flavors-dev.

✔ Which ios bundle id do you want to use for this configuration, e.g. 'com.example.app'? · com.codewithandrea.flutterfire-flavors.dev 
i Firebase ios app com.codewithandrea.flutterfire-flavors.dev is not registered on Firebase project flutterfire-flavors-dev.
i Registered a new Firebase ios app on Firebase project flutterfire-flavors-dev.
✔ Which macos bundle id do you want to use for this configuration, e.g. 'com.example.app'? · com.codewithandrea.flutterfire-flavors 
i Firebase macos app com.codewithandrea.flutterfire-flavors is not registered on Firebase project flutterfire-flavors-dev.
i Registered a new Firebase macos app on Firebase project flutterfire-flavors-dev.
i Firebase web app flutterfire_flavors (web) is not registered on Firebase project flutterfire-flavors-dev.
i Registered a new Firebase web app on Firebase project flutterfire-flavors-dev.

Note how it detected com.codewithandrea.flutterfire_flavors as the Android app (non-flavored version) and it asked for the bundle ID on iOS and macOS.

How I would like this to work

  • flutterfire should detect if the app is configured with multiple flavors, and ask if I want to use/create a separate Firebase project for each.
  • then it should create firebase_options_dev.dart, firebase_options_staging.dart, firebase_options_prod.dart files that can be imported in each of the entry points (in my case I have main_development.dart, main_staging.dart, main_production.dart).

Note: I use very_good_cli to automate the process of creating an app with multiple flavors, but the same applies if multiple flavors are created manually.

As it stands, the only reliable way of getting this to work is to:

  • create 3 mock Flutter projects to match the package names of each of my desired flavors
  • run flutterfire configure on each
  • rename and copy the generated firebase_options.dart into the main Flutter app for each flavor

It can be done, but it's error prone and the whole point of flutterfire is to automate all this configuration.

Any serious project would use multiple flavors, so I believe this is a very important feature to add.

bizz84 avatar Dec 13 '21 09:12 bizz84

This is technically possible already with using the CLI flags, e.g.:

flutterfire config \
  --project=mycoolproject-dev \
  --out=lib/firebase_options_dev.dart \
  --ios-bundle-id=com.codewithandrea.flutterfire-flavors.dev \
  --macos-bundle-id=com.codewithandrea.flutterfire-flavors.dev \
  --android-app-id=com.codewithandrea.flutterfire-flavors.dev

So the options are already in place - albeit maybe a little manual

If someone would like to PR to make this less manual then please do :)

Salakar avatar Dec 13 '21 10:12 Salakar

@Salakar that's great! That already takes much of the pain away as it needs to be run 3 times but at least it can be automated.

bizz84 avatar Dec 13 '21 12:12 bizz84

I'm going to close this now since I think this is essentially covered now with the above flags and along with the recent addition of the --yes flag (to skip prompts and accept defaults). Meaning this can be automated, e.g. defining commands in melos scripts to switch environments

Salakar avatar Mar 30 '22 12:03 Salakar

@Salakar that is not a solution, as there is no documentation describing how to do that, nor it seems trivial.

Running flutterfire config also creates a firebase_app_id_file.json file that needs to exist in each of android and ios folder. This file doesn't seem to allow for flavors, so I can't have them scoped like firebase_app_id_file_development.json.

JCQuintas avatar Apr 04 '22 19:04 JCQuintas

I agree with @JCQuintas. Plus those files were not generated by the first version of FlutterFire CLI, and when I delete google_services.json and the corresponding plugin configuration from android/app/build.gradle, and the firebase_app_id_file.json file from ios, everything seems to work fine without them. Why were those files added by default? What is their purpose? And if they are absolutely useful, you need to add command-line arguments to make it possible to override where these files will be generated so that they don't overwrite the last version when I run flutterfire configure for each of my flavor projects.

sarbogast avatar Apr 14 '22 13:04 sarbogast

Unfortunately these firebase_app_id_file.json files and .gradle file updates are required for native builds if you want certain plugins (like Analytics, Crashlytics & Performance) to function correctly on Android, iOS & macOS. Since these interact with native builds there isn't really a way to make them environment specific since the CLI would be unware of your Android/iOS build targets etc. Open to suggestions/pull requests on these.

Having said that, in the latest release I've just shipped two new flags (hidden), --no-apply-gradle-plugins and --no-app-id-json - which will disable these files being generated/changed should you need to opt-out, though I'd caution against doing so unless you know what you're doing with configuring the native parts of Android / iOS to work properly with the Firebase services mentioned above.

Salakar avatar Apr 19 '22 20:04 Salakar

The first thing is to document why those files can be necessary. For example, in the CLI documentation, on the line about --[no-]apply-gradle-plugin there should be a mention explaining why you might not need to apply the gradle plugin and create the google-services.json file. There are a lot of projects that only use firebase core, auth and firestore, but not analytics, crashlytics and performance.

Second of all, there should be an equivalent option to this one for the app-id-json and that's great that you added that. Is it already in the current version, but just undocumented?

You could also make it so that flutterfire configure doesn't generate any of those files by default, and then add a command to flutterfire that adds those files for native plugins, something like flutterfire configure-native-plugins, but maybe that's overkill.

Then the most important thing is to make is possible to customize the path and the name of those files, just like -out lets us do it for firebase_options.dart. I use that already to have 3 versions of this file in my project, one for each environment/flavor, that I import into each of my main.darts. In Android, google-services-json could be stored in flavor-specific subdirectories (android/app/src/dev or android/app/src/prod instead of the default android/app) so that Gradle picks up on the right version depending on the flavor. So maybe a command-line parameter like -android-native-out that accepts a full path for google-services.json.

And finally, I would like a command line parameter to customize where the app id json file is generated for ios, path and file name as well, which would allow me to have different versions of that file for my different environments. And then because Firebase native libraries look for this file in a specific place with a specific name, I can have a script in my XCode build pipeline that picks up the proper version of the file and copies it where Firebase is expecting it to be. That's already what we did for flavor builds before. Agreed, you don't need to document that process in the CLI documentation, but giving that option makes it more flexible.

By the way, I'm preparing a step-by-step tutorial on how to flavorize a Flutter project including web support and firebase support, so if those options are added, I will also document them there.

sarbogast avatar Apr 20 '22 04:04 sarbogast

By the way, I just tried to add the --no-apply-gradle-plugin flag, as described in the CLI documentation, but I got an error: Could not find an option named "no-apply-gradle-plugin"

And indeed, it is not documented in flutterfire help configure

sarbogast avatar Apr 20 '22 05:04 sarbogast

By the way, I just tried to add the --no-apply-gradle-plugin flag, as described in the CLI documentation, but I got an error: Could not find an option named "no-apply-gradle-plugin"

And indeed, it is not documented in flutterfire help configure

--no-apply-gradle-plugins (plural on plugins) there's an update for the docs waiting to fix the docs but we're in a docs change freeze right now sorry 🙈 it's a hidden flag so won't show on help commands currently but it is there - hidden until properly documented since we don't want anyone using it without knowing the full implications

Thanks for all the input previously also, I can certainly add options to change the output paths for both json files so let me look into that, the only caveat to this is that we need to note on the CLI that if you do this it won't work by default and you shouldn't do it unless you know what you're doing.

--no-app-id-json also exists in current version released yesterday, also hidden currently so won't show in --help. I wonder perhaps if these two flags should become one flag, something like --[no-]native-integration since effectively similar just ones for android and one ios.

Salakar avatar Apr 20 '22 06:04 Salakar

@Salakar In our app we have defined two flavors (ios schemes) but only one Firebase project. How can we achieve to have firebase_app_id_file.json per scheme?

One of the older project we have defined custom Build Phase which will copy correct GoogleServices.plist into project based on current scheme. Do we need something similar?

petrnymsa avatar May 10 '22 09:05 petrnymsa

Hey guy, as long as the flutterfire_cli does not support multiple schemes for the firebase_app_id_file.json, can we keep using our old google-services.json and the GoogleService-Info-***.plist files with the new version of firebase_core (^1.16.0) and firebase_crashlytics (^2.7.2), or is it better to revert to previous versions?

GitHelge avatar May 13 '22 14:05 GitHelge

Don't know anyone who doesn't use flavors for the production app, so this issue should be a priority otherwise what's the point.

enqvida avatar May 19 '22 13:05 enqvida

@GitHelge Yes it works, if you set up Firebase the old way.

enqvida avatar May 19 '22 13:05 enqvida

For all who are searching for a solution (also working in pipelines):

# Install Firebase-CLI
curl -sL firebase.tools | bash

# Install FlutterFire CLI
flutter pub global activate flutterfire_cli

# Configure FlutterFire for ios, android and web
flutterfire configure -y --project MYAPP-$SCHEME --android-app-id=$BUNDLE_ID --ios-bundle-id=$BUNDLE_ID --macos-bundle-id=$BUNDLE_ID --platforms=android,ios,web

Edit: I suppose you to know how to handle flavors with flutter_flavorizr to use this solution!

Ahmadre avatar May 23 '22 14:05 Ahmadre

Flutter Flavors are a feature of flutter overall that the CLI is completely oblivious to at the moment. We are going to have to stay on an older version of firebase, but as customers of firebase this is a major blocker to being able to adopt some of the new features.

djorgji avatar May 25 '22 19:05 djorgji

If you check out this article that I published recently, you will see that it's possible to work around it. The article is now also referenced in the documentation.

sarbogast avatar May 25 '22 19:05 sarbogast

@sarbogast this doesn't solve the issue with firebase_app_id_file.json file being not flavoured.

bounty1342 avatar May 27 '22 23:05 bounty1342

No but you only need this file in certain not so frequent situations. So at the very least for now you can disable its generation when you don't need it. Hopefully at some point we can customize its name and use a script to copy the right one. Or the libraries that need it for now won't need it anymore.

sarbogast avatar May 27 '22 23:05 sarbogast

I used https://medium.com/@animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b method to copy google-services.json with the old method. Maybe, something similar should work for the firebase_app_id_file.json.

Something like :

 environment="default"
 
 # Regex to extract the scheme name from the Build Configuration
 # 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 extract 
 # the environment (or flavor)
 # For eg.
 # If CONFIGURATION="Debug-prod", then environment will get set to "prod".
 if [[ $CONFIGURATION =~ -([^-]*)$ ]]; then
 environment=${BASH_REMATCH[1]}
 fi
 
 echo $environment
 
 # Name and path of the resource we're copying
 FIREBASEPPID_INFO_PLIST=firebase_app_id_file.json
 FIREBASEPPID_INFO_FILE=${PROJECT_DIR}/config/${environment}/${FIREBASEPPID_INFO_PLIST}
 
 # Make sure firebase_app_id_file.json exists
 echo "Looking for ${FIREBASEPPID_INFO_PLIST} in ${FIREBASEPPID_INFO_FILE}"
 if [ ! -f $FIREBASEPPID_INFO_FILE ]
 then
 echo "No firebase_app_id_file.json found. Please ensure it's in the proper directory."
 exit 1
 fi
 
 # Get a reference to the destination location for the firebase_app_id_file.json
 # This is the default location of firebase_app_id_file.json file
 PLIST_DESTINATION=${PROJECT_DIR}
 echo "Will copy ${FIREBASEPPID_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
 
 # Copy over the prod firebase_app_id_file.json for Release builds
 cp "${FIREBASEPPID_INFO_FILE}" "${PLIST_DESTINATION}"

Also instead of executed at the build phase, it should probably be place in the Pre-Action of the Scheme.

bounty1342 avatar May 27 '22 23:05 bounty1342

@Salakar my app was configured last year to use FlutterFire, in a manual process. At each release I always take a look at the Quick Start guide, to see if I need to change some configuration, json, gradle, etc.

On the last release the docs were migrated and updated to only have a CLI option for the configurations, but my app has several whitelabel versions and stages, which I can't find a documentation on how to configure those.

I think there are 2 scenarios here:

  1. Multiple Apps on the same project. This will happen with whitelabel apps, or free and paid versions of the same app, where they have different package ids/bundle ids, but they all use the same crashlytics or remote config.

  2. Multiple Apps on different projects. This will happen with different stages of release (development, tests, production), so we could have a database segregation and not impact customers if we are playing with the database.

The CLI must support both of these scenarios, which can include flavors and dimensions on Android or Schemes and Targets on iOS. I can't see how could I configure 4 different Android package ids for the same project using the flags you provided before, as I have several flavors and dimensions.

For now I just updated the packages versions on pubspec.yaml, but since I can't run the CLI and have dart initialization, I imagine my Crashlytics reports won't be optimized for Flutter.

feinstein avatar Jun 15 '22 19:06 feinstein

Unfortunately these firebase_app_id_file.json files and .gradle file updates are required for native builds if you want certain >plugins (like Analytics, Crashlytics & Performance) to function correctly on Android, iOS & macOS.

I am using Crashlytics (who Flutter/Firebase developer isn't?) so if I understood this correctly, I cannot remove google-services.json from my Android project after all. It is unclear can I still remove google-services related stuff from .gradle files as documented here. From the iOS project I can remove GoogleService-Info.plist, but I need to add a similar file firebase_app_id_file.json. So things do not get any simpler with iOS either especially with the flavors I have already configured years ago.

So I need google-services.json and similar config file for iOS as earlier, plus now there is also a new file firebase_options.dart which only contains duplicate information to what is already available in google-services.json and GoogleService-Info.plist (or firebase_app_id_file.json).

Maybe and hopefully this new command line tool helps new Flutter developers, but for me this seems only like a new problem and more work without any additional value.

UPDATE: It seems that GoogleService-Info.plist cannot be removed either. It is required by at least the plugin google_sign_in (there is no Firebase app without google_sign_in). Debug log also has other warnings from Firebase/Core and Firebase/Analytics complaining that GoogleService-Info.plist cannot be found. So I really don't understand what is this new documentation about "Dart-only initialization". It is not possible for a real world Flutter/Firebase app.

I summarized my findings and suggestions here: https://github.com/invertase/flutterfire_cli/issues/85

kinex avatar Jun 21 '22 21:06 kinex

Just to resume a bit the discussion for a typical simple project with dev and prod (no staging here to make it simple) :

When running firebasecli configure:

  • There is one file firebase_options.dart created in the dart directory
  • There are google-services.json and GoogleService-Info.plist created in the native directories

The following command allows to create multiple versions of the json and plist:

flutterfire config \
  --project=mycoolproject-dev \
  --out=lib/firebase_options_dev.dart \
  --ios-bundle-id=com.mycoolproject.app.dev \
  --macos-bundle-id=com.mycoolproject.app.dev \
  --android-app-id=com.mycoolproject.app.dev

However this is necessary only for crashalytics and and analytics. In the event that someone only has dev and prod, where dev does not need crashalytics nor analytics, the following strategy can be used:

  • use flutterfire cli to configure project with prod so the json and plist files are the ones for prod
  • use flutterfire cli to generate only firebase_options_dev that will be used by the dev version

Is the above correct ?

cedvdb avatar Jun 22 '22 10:06 cedvdb

I am not sure how those commands will deal with flavors, since we need multiple json/plist in different native folders. Example: one json in the Android prod folder and another in the dev folder.

feinstein avatar Jun 22 '22 12:06 feinstein

All that is needed if for the command to let us customize the path and/or name of those files so that we can then add our own scripts to copy the right version to the location and name expected by the native plugins, just like we used to do with legacy configuration.

sarbogast avatar Jun 22 '22 12:06 sarbogast

Could a PR eventually land to remove the need for the json and plist files eventually ? Similar to this one https://github.com/flutter/plugins/pull/5250

cedvdb avatar Jun 24 '22 22:06 cedvdb

@bizz84 what's your thoughts on this now?

neiljaywarner avatar Jun 30 '22 19:06 neiljaywarner

For what it's worth, I resolved this issue by running flutterfire config multiple times. if you run flutterfire configure once for each flavor, and then move the resulting google-services.json into android/app/src/${flavorName}, then the build succeeds for each flavor. A big caveat here is that I have only tested this on Android so far, not on iOS. I haven't looked into what steps will be necessary to get the different flavors working on iOS with flutterfire.

sdstolworthy avatar Jul 06 '22 12:07 sdstolworthy

For android it does work like you said @sdstolworthy but for iOS the cli only outputs one file ios/firebase_app_id_file.json and it will overwrite it each time. We can manually fix it for iOS.

There is a warning printed when doing pod install:

Warning: firebase_app_id_file.json file does not exist. This may cause issues in upload-symbols. If this error is unexpected, try running flutterfire configure again.

I guess it can be ignored as long as you're sure you have set things up correctly.

What I did:

  • Ran the configure command multiple times, making sure to copy the ios/firebase_app_id_file.json file into my respective flavor folder each time. This is the same folder I store my GoogleService-Info.plist file for each flavor.
    • ios/Runner/Firebase/Prod/firebase_app_id_file.json
    • ios/Runner/Firebase/Sandbox/firebase_app_id_file.json
  • Added an xcode run script build phase which copies the correct firebase_app_id_file.json file into the root ios/ folder, just like the one for GoogleService-Info.plist works.
  • In my main.dart I use package_info_plus to know my package name to determine what flavor is running and configure firebase with the correct file accordingly:
import 'package:app/firebase_options_prod.dart';
import 'package:app/firebase_options_sandbox.dart';
import 'package:package_info_plus/package_info_plus.dart';

Future<void> main() async {
  late final PackageInfo packageInfo;
  try {
    packageInfo = await PackageInfo.fromPlatform();
  } catch (e, s) {
    /// Fallback value
    packageInfo = PackageInfo(
      appName: 'MyApp',
      packageName: 'com.myapp.prod',
      buildNumber: '0',
      version: '0.0.0',
    );
  }

  final isSandbox = packageInfo.packageName == 'com.myapp.sandbox';

  await Firebase.initializeApp(
  options: isSandbox
      ? firebase_sandbox.DefaultFirebaseOptions.currentPlatform
      : firebase_prod.DefaultFirebaseOptions.currentPlatform,
  );
}

Here is my run script I used:

# Name of the resource we're selectively copying
FIREBASE_APP_ID_FILE=firebase_app_id_file.json

# Get references to sandbox and prod versions of firebase_app_id_file.json
# NOTE: These should only live on the file system and should NOT be part of the target (since we'll be adding them to the target manually)
FIREBASE_APP_ID_FILE_SANDBOX=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Sandbox/${FIREBASE_APP_ID_FILE}
FIREBASE_APP_ID_FILE_PROD=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Prod/${FIREBASE_APP_ID_FILE}

# Make sure the sandbox version of firebase_app_id_file.json exists
echo "Looking for ${FIREBASE_APP_ID_FILE} in ${FIREBASE_APP_ID_FILE_SANDBOX}"
if [ ! -f $FIREBASE_APP_ID_FILE_SANDBOX ]
then
    echo "No sandbox firebase_app_id_file.json found. Please ensure it's in the proper directory."
    exit 1
fi

# Make sure the prod version of firebase_app_id_file.json exists
echo "Looking for ${FIREBASE_APP_ID_FILE} in ${FIREBASE_APP_ID_FILE_PROD}"
if [ ! -f $FIREBASE_APP_ID_FILE_PROD ]
then
    echo "No prod firebase_app_id_file.json found. Please ensure it's in the proper directory."
    exit 1
fi

# Get a reference to the destination location for firebase_app_id_file.json
FILE_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${FIREBASE_APP_ID_FILE} to final destination: ${FILE_DESTINATION}"

# Copy over the correct firebase_app_id_file.json for the current build configuration
if [ "${CONFIGURATION}" == "Debug-prod" ] || [ "${CONFIGURATION}" == "Release-prod" ] || [ "${CONFIGURATION}" == "Profile-prod" ]
then
    echo "Using ${FIREBASE_APP_ID_FILE_PROD}"
    cp "${FIREBASE_APP_ID_FILE_PROD}" "${FILE_DESTINATION}"
elif [ "${CONFIGURATION}" == "Debug-sandbox" ] || [ "${CONFIGURATION}" == "Release-sandbox" ] || [ "${CONFIGURATION}" == "Profile-sandbox" ]
then
    echo "Using ${FIREBASE_APP_ID_FILE_SANDBOX}"
    cp "${FIREBASE_APP_ID_FILE_SANDBOX}" "${FILE_DESTINATION}"
else
    echo "Error: invalid configuration specified: ${CONFIGURATION}"
fi

At build/run time, it all works as expected because there's only one ios/firebase_app_id_file.json file and it's in the correct spot at build time thanks to the run script. Depending which flavor I select, it copies the right one into place just like it does for the GoogleService-Info.plist file.

This is a tough thing to solve in the cli tool because flavors are by design such a flexibly-implemented thing - people use all sorts of different approaches. I think there might just need to be a good doc page outlining the approach for setting it up for flavors and it's up to the user to adapt it to their specific implementation.

acoutts avatar Jul 11 '22 15:07 acoutts