Use two Firebase instances in your Android App and never test in production

Use two Firebase instances in your Android App and never test in production

For two days, I see captures and memes about the error that occurred in the Airbnb mobile application. A notification with the message "Test dev" was sent to several users.

I don't know how it happened, but I'm sure this time it's not an intern 😅 anyway ! In this article I'm going to show you how at BeServed, we avoid these kinds of errors by creating two Firebase instances for a single Android project, one of which is for testing and one for production.

By default, when we want to add a Firebase project to our Android application, the Firebase guide asks us to add the plugin com.google.gms.google-services to our app build.gradle file, to download and put the google-services.json file in the app folder and thanks to that your application will be linked to the Firebase project.

Now the question is 👇🏾

There are two possibilities,

1st option : using multiple google-services.json

The first one is to use a google-services.json file for each build variant. Indeed, you can place several google-services.json files each in a folder named according to the build variant it belongs to.

For example, if you have product flavors "dev" and "release", you can organize them as follows :

app/
    google-services.json
    src/dev/google-services.json
    src/release/google-services.json
...

The google-service.json file is processed into Android string resources by the Google Services gradle plugin. You can see which resources are created in the Google Services Plugin documentation on Processing the JSON file.

To learn more, see the Google Services Plugin documentation on Adding the JSON file.

These resources are then loaded by the FirebaseInitProvider which runs before your application code and initializes Firebase APIs using those values.

2nd option : checking at runtime with Firebase options object

As you my already know, the JSON file is processed at build time, and it's read into an options object (FirebaseOptions) that is referenced by the Firebase application object (FirebaseApp). This option consists in adding the string resources directly to your app instead of using the Google Services Gradle plugin. And this is how we do it at BeServed.

First you should

  • Remove the google-services plugin from your root build.gradle
  • Delete the google-services.json from your project
  • Delete apply plugin: com.google.gms.google-services from your app build.gradle

After that, you need to create a Firebase options object to hold the configuration data for the Firebase application. It uses the builder pattern, and you can specify a couple of information, as in the example below.

val options =  FirebaseOptions.Builder()
        .setProjectId("my-firebase-project")
        .setApplicationId("1:27992087142:android:ce3b6448250083d1")
        .setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
        .build()

Project ID, Application ID and API key are required fields, but there’s a lot of other that can be found in the API reference documentation

After creating an option variable for each instance, this is how our Application class should look like :

class App : Application() {
    override fun onCreate() {  
        super.onCreate()
        val devOptions =  FirebaseOptions.Builder()
                .setProjectId("beserved-development-project")
                .setApplicationId("1:212877806437:android:fee5f52927e2f7a907ed8c")
                .setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
                .build()

        val prodOptions =  FirebaseOptions.Builder()
                .setProjectId("beserved-production-project")
                .setApplicationId("1:27992087142:android:ce3b6448250083d1")
                .setApiKey("BIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
                .build()

        // here we check wich instance to use depending on the build type        
        if(BuildConfig.DEBUG) {
            FirebaseApp.initializeApp(this, devOptions, "beserved-development")
        } else {
            FirebaseApp.initializeApp(this, prodOptions, "beserved-production")
        }
    }
}

Now our app will dynamically switch between Firebase projects and the third parameter of the initializeApp()is the instance name, so if for example we want to use the Firestore we should provide the name of the corresponding instance, and we can also change this name dynamically by checking if the BuildConfig is DEBUG or not.

val instanceName = if (BuildConfig.DEBUG) "beserved-development" else "beserved-production"
FirebaseApp.getInstance(instanceName)

The next challenge is to keep settings synchronized on both test and development instances in order to avoid unexpected behavior when you switch between build variants. But this will prevent you to test in production, event if you're an intern !!

I hope this article was helpful !

Some References