Using multiple Firebase projects in your Android App
Firebase Android SDK is designed to work with one Firebase project by default. But it is quite possible to access multiple projects in a single App due to different reasons. One common case in the industry is, for example, the requirement for having multiple development environments or delivery stages. In this article, I want to share my experience with you and summarize the different ways how to achieve this with the Firebase Android SDK.
Using different config files per build variant
As described in the official Firebase documentation, it is possible to use different configuration files for each build type. The only thing you need to do is to put each google-services.json
file into the root of the regarding build variant folder. Assume that you have 3 environments — development, alpha and release— and for each a build variant in your project. Then you have to place the google-services.json
files like this:
app/
google-services.json
src/development/google-services.json
src/alpha/google-services.json
src/release/google-services.json
Depending on the build variant you’ve chosen for building your App, the corresponding configuration will be loaded by the SDK for initializing the default FirebaseApp
instance. The main advantage of this approach is that you don’t need to change anything in your code, you just can get the necessary Firebase services by calling the default getInstance()
method.
Using string resources for configuration
Referring again to the official Firebase documentation, it is possible to use string resources instead of putting google-services.json
files in your project, in order to get Firebase configured properly for the desired build variant. In that case, you need to initialize the default FirebaseApp
instance programmatically calling the FirebaseApp.initializeApp()
method once at app start (in onCreate()
method of your Application
class, for example):
FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId(getString(R.string.firebaseApplicationId))
.setApiKey(getString(R.string.firebaseApiKey))
.setDatabaseUrl(getString(R.string.firebaseDatabaseUrl))
.build();
FirebaseApp.initializeApp(this /* Context */, options);
You need to define, of course, the necessary strings in your string resource files in advance. Assuming again, that you have the 3 environments from the previous section, you could define these strings in firebase_strings.xml
file in the /res/values
folder of the regarding build variant folder, like the following:
app/
src/development/res/values/firebase_strings.xml
src/alpha/res/values/firebase_strings.xml
src/release/res/values/firebase_strings.xml
In each firebase_strings.xml
file you need to define then the necessary configuration info:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="firebaseApplicationId" translatable="false">1:530266078999:android:481c4ecf3253701e</string>
<string name="firebaseApiKey" translatable="false">AIzaSyBRxOyIj5dJkKgAVPXRLYFkdZwh2Xxq51k</string>
<string name="firebaseDatabaseUrl" translatable="false">https://project-1765055333176374514.firebaseio.com/</string>
</resources>
Here you may ask where do we get the application id, API key, and the database URL. You can get this information simply from your google-services.json
file. The application id is the value of the mobilesdk_app_id
element, the API key is the value of the current_key
element, and the database URL is the value of the firebase_url
element of the JSON object. Alternatively, you could obtain this information also from the Firebase console.
Configuring Firebase dynamically for different environments
The previous two approaches have the advantage, that you don’t need to touch your code, you just build your application for the desired environment and that is it. But imagine that for testing purposes, you have the requirement that it should be possible to change the environment within the app at runtime. This won’t work with the previous two approaches. Even killing the app and starting it again won’t help, you have to install the necessary build variant in order to switch the environment.
For that purpose, you have to initialize and maintain the for each environment a FirebaseApp
instance respectively in your app and I want to show you how I achieved this functionality in my recent project.
The basic approach
The simplest way to achieve this functionality is to initialize necessary FirebaseApp
instances once at app start:
FirebaseApp development = Firebase.initializeApp(context, developmentOptions, Environment.DEVELOPMENT);
FirebaseApp alpha = Firebase.initializeApp(context, alphaOptions, Environment.ALPHA);
FirebaseApp release = Firebase.initializeApp(context, releaseOptions, Environment.RELEASE);
Then you use depending on your current environment the regarding FirbaseApp
instance. Depending on your UI logic, you will need to call the same routine when the user changes the environment:
Firebase currentApp;
switch(environment) {
case Environment.DEVELOPMENT:
currentApp = development;
break;
case Environment.ALPHA:
currentApp = alpha;
break;
case Environment.RELEASE:
currentApp = release;
break;
default:
throw new RuntimeException();
break;
}
Before using any Firebase service, you need to get its instance with that FirebaseApp
instance:
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(currentApp);
FirebaseDatabase firebaseDatabase = FirebaseDatabase.getInstance(currentApp);
Of course, depending on your architecture, you need to find the best way to integrate the routines for selecting the correct FirebaseApp
instance and getting the needed Firebase services and encapsulate them in a proper way.
On-Demand initialization
As a next step, in order to save some memory resources, you can think of on-demand initialization of the FirebaseApp
instances. But this is a little bit tricky task because an attempt for checking if a particular FirebaseApp
instance was initialized previously or not using FirebaseApp.getInstance()
will fail:
// This will throw an exception, if the FirebaseApp
// for that environment wasn't initialized previously:
FirebaseApp currentApp = FirebaseApp.getInstance(environment);
In order to avoid this, an extra mechanism is needed for knowing, if a particular FirebaseApp
instance was initialized or not. One possibility, which I want to propose, is to use a Map
holding the FirebaseApp
instances for the different environments. We need to adjust the basic approach, then, like this:
Map<String, FirebaseApp> firebaseApps = new HashMap<>();...if (!firebaseApps.containsKey(environment)) {
FirebaseApp firebaseApp = FirebaseApp.initializeApp(context, environmentOptions, environment);
firebaseApps.put(environment, firebaseApp);
}...FirebaseApp currentApp = firebaseApps.get(environment);...FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(currentApp);
This way, you don’t even need the ugly switch
statement. The only thing you have to think about is how to provide the correct configuration info with the environmentOptions
object. For that, you could use enum
‘s instead of String
constants, with a field ofFirebaseOptions
, which you can access directly while calling FirebaseApp.initializeApp()
method. Here how the enum class could look like:
public enum Environment {
development(new FirebaseOptions.Builder()
.setApplicationId("1:530266078999:android:481c4ecf3253701e")
.setApiKey("AIzaSyBRxOyIj5dJkKgAVPXRLYFkdZwh2Xxq51k")
.setDatabaseUrl("https://project-1765055333176374514.firebaseio.com/")
.build()
),
alpha(new FirebaseOptions.Builder()
.setApplicationId("1:530266078999:android:481c4ecf3253701e")
.setApiKey("AIzaSyBRxOyIj5dJkKgAVPXRLYFkdZwh2Xxq51k")
.setDatabaseUrl("https://project-1765055333176374514.firebaseio.com/")
.build()
),
release(new FirebaseOptions.Builder()
.setApplicationId("1:530266078999:android:481c4ecf3253701e")
.setApiKey("AIzaSyBRxOyIj5dJkKgAVPXRLYFkdZwh2Xxq51k")
.setDatabaseUrl("https://project-1765055333176374514.firebaseio.com/")
.build()
); @NonNull
public final FirebaseOptions firebaseOptions; Environment(@NonNull FirebaseOptions.Builder optionsBuilder) {
firebaseOptions = optionsBuilder.build();
}
}
And here the adjusted routine for on-demand initialization:
Map<Environment, FirebaseApp> firebaseApps = new HashMap<>();...if (!firebaseApps.containsKey(environment)) {
FirebaseApp firebaseApp = FirebaseApp.initializeApp(context, environment.firebaseOptions, environment.name());
firebaseApps.put(environment, firebaseApp);
}...FirebaseApp currentApp = firebaseApps.get(environment);...FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(currentApp);
Summary
In most of the cases, a simple configuration using google-services.json
will be enough for having different Firebase environments in your App. But as we saw, things can get complex very quickly, and depending on the requirements custom ways for configuration and initialization of the Firebase services can be needed. I this article I tried to summarize the different approaches for achieving this goal and share my personal experience. I hope that it was helpful to you and that you enjoyed it.