1

Has anyone figure out how to switch Firebase project/environments inside the app, say you had a drop down with different dev URLs. We are not talking about different builds, that won't work for us and would require sending multiple versions to TestFlight.

There's a snippet of code we found here.

final options = FirebaseOptions.from({});
final firebaseApp = await FirebaseApp.configure(name: 'some_app_name', options: options);

But we can't figure out how to use it, and don't know if it works correctly.

Edit

It was not officially supported when I wrote this. It now is.

Oliver Dixon
  • 7,012
  • 5
  • 61
  • 95

2 Answers2

6

My approach to multiple environments - use multiple projects, and a .env file to determine which to use.

Project setup

  1. I used Flutterfire CLI to set up a Firebase project:
flutterfire configure
  1. After the project is created, I renamed the config files to represent the environment:
mv android/app/google-services.json android/app/google-services-<env>.json
mv ios/firebase_app_id_file.json ios/firebase_app_id_file-<env>.json 
mv lib/firebase_options.dart lib/firebase_options_<env>.dart

Repeat steps 1-2, for each environment you need.

Environment variable setup

  1. Used flutter_dotenv to load an environment variable used to determine the environment: .env:
# Set choose environment. Options: production, stage, etc...
ENVIRONMENT=production
  1. Don't forget to add .env to pubspec.yaml
flutter:
  assets:
    - .env

Connect the environment set in .env

  1. Update main.dart to import env configs, and flutter_dotenv:

main.dart:

import 'firebase_options_prod.dart' as prod;
import 'firebase_options_stage.dart' as stage;
import 'package:flutter_dotenv/flutter_dotenv.dart';
  1. Load .env the following line before you initialize Firebase:

main.dart:

await dotenv.load(fileName: '.env');
  1. Pass in the appropriate config based on the value ENVIRONMENT:

main.dart:

await Firebase.initializeApp(
  options:
    dotenv.env['ENVIRONMENT'] == 'stage' ? stage.DefaultFirebaseOptions.currentPlatform :
    prod.DefaultFirebaseOptions.currentPlatform);

Here is main() in its entirety:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load(fileName: '.env');
  try {
    await Firebase.initializeApp(
      options:
        dotenv.env['ENVIRONMENT'] == 'stage' ? stage.DefaultFirebaseOptions.currentPlatform :
        prod.DefaultFirebaseOptions.currentPlatform);
  } catch(exception) {
    if(exception is FirebaseException && exception.code == 'duplicate-app') {
      debugPrint("Did you forget to recompile the Runner app, after changing environments?");
    }
    rethrow;
  }
  runApp(const MyApp());
}

Note the try/catch/rethrow added in. I added this because when you change environments, you cannot hot restart. You need to recompile the runner app. If you do not, and then you change environments, Firebase throw an error that looks like this:

Unhandled Exception: [core/duplicate-app] A Firebase App named "[DEFAULT]" already exists

So I wanted an easy reminder of why I might see that error.

Jared Anderton
  • 826
  • 9
  • 12
  • I like this approach. Can you please share the sample full code if possible? where should i setup my 3 different environments here. for ex: dev, stage, prod. "ENVIRONMENT=production" -> i am not able to follow this instruction. other than that, how can i pass the different environment values from command line and vs code ide? – James Mark Sep 09 '22 at 01:48
  • The .env file is a file that's ignored in my repos. For dev env, I manually create a copy of it in my project, containing dev env vars. For my stage & production builds, my CI pipeline creates a .env file, using env vars that are injected into the build system (like a CircleCI env var, or a Github Action Secret). Depending on the project, the file is either A) created totally in the CI job steps, by echoing the vars into the the file or B) I have an .env.dist file, with placeholders, make a copy, and use [envsubst](https://stackoverflow.com/a/11050943/12132021) populate values. Hope that helps – Jared Anderton Sep 16 '22 at 04:06
  • One other thing I will say - the `.env` is good for environment *variables*. You can put anything that is okay to be public; but it is not okay to load sensitive secrets in the `.env` file. You can _easily_ unpack an ipa or apk, and find .env in *plain text*. If you have anything sensitive, keep it on a server. An example of what I mean: [Stripe has 2 kinds of keys, publishable (client side) and secret (server side)](https://stripe.com/docs/keys#obtain-api-keys). You could put the publishable key in the `.env` file, but putting secret in the `.env` would compromise your account – Jared Anderton Sep 16 '22 at 04:12
  • how do you recompile your runner app after changing environments? I keep running into the duplicate app error and the only way I could prevent it was by providing a name in `Firebase.initializeApp`. – Nathan Tew Sep 20 '22 at 02:45
  • I just stop the app, and recompile with like `flutter run` but, the key thing is that you have to run xcode build or android's gradle build (its a native recompile; e.g. not a flutter hot restart or hot reload). I have also noticed that if I had an app that had a single environment, then added a second, I would get the same issue. To fix it, I had to run `flutter clean` AND I had to delete and reinstall the app from the device/simulator. – Jared Anderton Sep 20 '22 at 03:56
  • 1
    also, at step 2, where are the renamed files referenced? apart from `firebase_options`, none of the new files are referenced anywhere in the project...? I ran `flutterfire configure` to connect my app to a dev project I set up, and I ensured that the 3 config files have changed, but my app somehow still communicates with the original, prod project on firebase. Could it have to do with the `GoogleService-Info.plist` file? – Nathan Tew Sep 20 '22 at 06:49
  • @JaredAnderton alright, for the duplicate env problem, I'll try deleting and re-installing the app, which is the only thing I haven't tried. Thanks! – Nathan Tew Sep 20 '22 at 06:50
  • It could be the `GoogleService-Info.plist` I don't have one of those in my project, after setting up with `flutterfire configure`. As for the different options, check step 5, where you import each firebase_options - the `import "..." as stage`, then I used logic in `initializeApp` to use the correct one, based off the environment – Jared Anderton Sep 20 '22 at 19:30
  • @JaredAnderton yes I get that you import different firebase options files, but what about `firebase_app_id_file' and 'google-services'? Where are those files referenced? – Nathan Tew Sep 20 '22 at 22:36
1
final FirebaseApp app = await FirebaseApp.configure(
    name: 'test',
    options: const FirebaseOptions(
      googleAppID: '1:79601577497:ios:5f2bcc6ba8cecddd',
      gcmSenderID: '79601577497',
      apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI-G72PVU',
      projectID: 'flutter-firestore',
    ),
  );
  final Firestore firestore = Firestore(app: app); // this line is imp

Use firebase core package for it to work, https://pub.dev/packages/firebase_core For more info: https://firebase.google.com/docs/projects/multiprojects#node.js

Niteesh
  • 2,742
  • 3
  • 12
  • 33
  • then you can use the firestore object to run the different related functions – Niteesh Jun 29 '20 at 13:41
  • thank you for this. I can then reference firestore elsewhere in the app? Like 'instance' for example? This works with everything? If so AMAZING! – Oliver Dixon Jun 29 '20 at 15:27
  • nah you can't use that, you'll have to pass the object through the constructors or consider using provider package to make it globally available. – Niteesh Jun 29 '20 at 17:08
  • So basically, just a global instance will suffice? – Oliver Dixon Jun 29 '20 at 18:04
  • 1
    @OliverDixon yeah – Niteesh Jun 29 '20 at 18:09
  • 1
    @Niteesh @OliverDixon does this work with FirebaseMessaging? It seems the `instanceFor` method is not available? Also, would I still need to have a google-services.json file or can I get rid of it? – HPage Mar 16 '21 at 17:55