Search code examples
androidfirebasegradlefirebase-app-distribution

Is there a way to configure separate serviceCredentialsFile for each Android build variant with Firebase App Distribution?


We have several Firebase projects which share the same code base via build types and flavors. We aim to use the app distribution via Gradle and authenticate using service account credentials.

In the docs, it is shown that firebaseAppDistribution block can be used to configure the parameters and the service credential file path is one of them. Since each variant is a Firebase project and each project has its own service credentials, as far as I can understand we need to point to separate service credential file paths in the Gradle configuration.

I've tried to update the file path with a gradle task depending on the variant but couldn't make it work. The current build file looks like:

...

apply plugin: 'com.google.firebase.appdistribution'

class StringExtension {
  String value

  StringExtension(String value) {
    this.value = value
  }

  public void setValue(String value) {
    this.value = value
  }

  public String getValue() {
    return value
  }
}

android {

  ...

  productFlavors.whenObjectAdded {
    flavor -> flavor.extensions.create("service_key_prefix", StringExtension, '')
  }

  productFlavors {

    flavor1 {
      ...
      service_key_prefix.value = "flavor1"
    }

    flavor2 {
      ...
      service_key_prefix.value = "flavor2"
    }
  }

  buildTypes {

    debug {
      firebaseAppDistribution {
        releaseNotesFile = file("internal_release_notes.txt").path
        groupsFile = file("group_aliases_debug_fb.txt").path
      }
    }

    release {
      firebaseAppDistribution {
        releaseNotesFile = file("release_notes.txt").path
        groupsFile = file("group_aliases_prod_fb.txt").path
      }
    }
  }
}

...

android.applicationVariants.all { variant ->

  task("firebaseCredentials${variant.name.capitalize()}", overwrite: true) {
    variant.productFlavors.each { flavor ->

      doLast {
        firebaseAppDistribution {

          def serviceKeyFile = file(
              "../${flavor.service_key_prefix.value}-${variant.buildType.name}-service-key.json")
          if (serviceKeyFile != null) {
            serviceCredentialsFile = serviceKeyFile.path
          }
        }
      }
    }
  }
}

android.applicationVariants.all { variant ->
  def distTask = tasks.named("appDistributionUpload${variant.name.capitalize()}")
  def configTask = tasks.named("firebaseCredentials${variant.name.capitalize()}")
  distTask.configure {
    dependsOn(configTask)
  }
}

apply plugin: 'com.google.gms.google-services'

The tasks seem to run correctly but I guess the file path is not updated because it still gives the following error when I run appDistributionUpload :

Could not find credentials. To authenticate, you have a few options:

  1. Set the serviceCredentialsFile property in your gradle plugin

  2. Set a refresh token with the FIREBASE_TOKEN environment variable

  3. Log in with the Firebase CLI

  4. Set service credentials with the GOOGLE_APPLICATION_CREDENTIALS environment variable

Any ideas on how to achieve such distribution configuration?


Solution

  • After contacting the support team, I've learned that this configuration is not available yet out of the box and exists as a feature request as of now.

    Here is the workaround from the Firebase support team, which modifies the related environment variable in memory at the beginning of each upload task:

    android {
    
      applicationVariants.all { variant ->
    
        final uploadTaskName = "appDistributionUpload${variant.name.capitalize()}"
        final uploadTask = project.tasks.findByName(uploadTaskName)
    
        if (uploadTask != null) {
          uploadTask.doFirst {
            resetCredentialsCache()
            final value = "$projectDir/src/${variant.name}/app-distribution-key.json"
            setEnvInMemory('GOOGLE_APPLICATION_CREDENTIALS', value)
          }
        }
      }
    }
    
    private static void setEnvInMemory(String name, String val) {
      // There is no way to dynamically set environment params, but we can update it in memory
      final env = System.getenv()
      final field = env.getClass().getDeclaredField("m")
      field.setAccessible(true)
      field.get(env).put(name, val)
    }
    
    private static void resetCredentialsCache() {
      // Google caches credentials provided by environment param, we are going to reset it
      final providerClass = Class.forName(
          'com.google.firebase.appdistribution.buildtools.reloc.com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider')
      final providerCtor = providerClass.getDeclaredConstructor()
      providerCtor.setAccessible(true)
      final provider = providerCtor.newInstance()
    
      final credsClass = Class.forName(
          'com.google.firebase.appdistribution.buildtools.reloc.com.google.api.client.googleapis.auth.oauth2.GoogleCredential')
      final field = credsClass.getDeclaredField('defaultCredentialProvider')
      field.setAccessible(true)
      field.set(null, provider)
    }
    

    EDIT: The above solution works only for app distribution plugin version 1.3.1.

    I didn't contact the support further and used GOOGLE_APPLICATION_CREDENTIALS environment variable on Jenkins CI, as explained here