3

Answered here.

I have more than one flavor in my app and I want to use the same google-service.json for all of them, so I've thought about set the value of the attribute package_name as a regular expression and replace it using a task in my build.gradle (app module).

My flavors are defined on this way:

android {
    productFlavors {
        FirstFlavor {
            applicationId "com.thisapp.first"
            versionCode = 1
            versionName "1.0"
        }
        SecondFlavor {
            applicationId "com.myapp.second"
            versionCode = 1
            versionName "1.0"
        }
    }
}

My idea was something like:

task runBeforeBuild(type: Exec) {
    def google_json = file('./google-services.json')
    google_json.getText().replace('${package_name_value}', myPackageName)
}

The problem is I don't know how to access to the PackageName (myPackageName in the code) or if is even possible.

Maybe I have to use another task instead of runBeforeBuild, I'm not very familiar with Gradle.

Óscar
  • 1,143
  • 1
  • 19
  • 38

2 Answers2

4

I've found another way to do it, and wanted to share it. Please note that this is my first time writing some tasks using gradle, so the code is not optimal at all (and I can't spend more time on it to improve it for now).

Explanation

What I'm doing is pretty simple.

1) Just before the task processFlavorBuildTypeGoogleServices, that is the task from Google Services that will read the google-services.json file, I trigger some code that will update the google-services.json file. In order to do that :

gradle.taskGraph.beforeTask { Task task ->
    if (task.name.startsWith("process") && task.name.endsWith("GoogleServices")) {
    }
}

2) Retrieve the current flavor and buildType from the task name (example of a task name: processProdReleaseGoogleServices in the form of process'Flavor''BuildType'GoogleServices)

String currentFlavor = task.name.replace("process", "").replace("GoogleServices", "")
currentFlavor = currentFlavor.toLowerCase()

3) Remove the buildType from the currentFlavor variable. In order to do that, I simply loop through all the buildTypes in my project, and remove them from the currentFlavor variable

android.applicationVariants.all { variant ->
    currentFlavor = currentFlavor.replace(variant.buildType.name, "")
}

At this point, the variable currentFlavor has the currentFlavor (for example "prod")

4) Retrieve the package name from the flavors defined in my build.gradle

In my build.gradle, I specify the packageName for each flavor:

productFlavors {
    prod {
        applicationId 'packageName1'
    }
    rec {
        applicationId 'packageName2'
    }
}

And I retrieve it like this: (The package name is returned with [], so I have to remove them. For example I would retrieve [packageName1])

String currentApplicationId;
android.applicationVariants.all { variant ->
    if (variant.flavorName == currentFlavor) {
        currentApplicationId = variant.productFlavors.applicationId.toString().replace("[", "").replace("]", "")
    }
}

5) Now that I have the package name of the current build, I just have to open the current google-services.json file, and update the package name inside. For that I added a method updateGoogleServicesJsonFile. Be careful to change the filePath on the second line to point to your location.

def updateGoogleServicesJsonFile(applicationId) {
    File file = new File(getProjectDir(), "/google-services.json")
    if (!file.exists())
    {
        project.logger.log(LogLevel.ERROR, "Error updating the google-services.json because the file doesn't exists...")
        return
    }

    List<String> lineList = file.readLines()
    for (int i = 0; i < lineList.size(); i++)
    {
        if (lineList.get(i).trim().startsWith("\"package_name\": \""))
        {
            String line = lineList.get(i)
            line = line.substring(0, line.indexOf(":") + 1)
            line += " \"" + applicationId + "\""

            lineList.set(i, line)
        }
    }

    file.write(lineList.join("\n"))
}

And there you have it, some code to update the google-services.json file just before the task to read it is executed.


Code

def updateGoogleServicesJsonFile(applicationId) {
    File file = new File(getProjectDir(), "/google-services.json")
    if (!file.exists())
    {
        project.logger.log(LogLevel.ERROR, "Error updating the google-services.json because the file doesn't exists...")
        return
    }

    List<String> lineList = file.readLines()
    for (int i = 0; i < lineList.size(); i++)
    {
        if (lineList.get(i).trim().startsWith("\"package_name\": \""))
        {
            String line = lineList.get(i)
            line = line.substring(0, line.indexOf(":") + 1)
            line += " \"" + applicationId + "\""

            lineList.set(i, line)
        }
    }

    file.write(lineList.join("\n"))
}

gradle.taskGraph.beforeTask { Task task ->
    // Before the task processFlavorBuildTypeGoogleServices (such as processProdReleaseGoogleServices), we update the google-services.json
    if (task.name.startsWith("process") && task.name.endsWith("GoogleServices")) {
        // Getting current flavor name out of the task name
        String currentFlavor = task.name.replace("process", "").replace("GoogleServices", "")
        currentFlavor = currentFlavor.toLowerCase()

        android.applicationVariants.all { variant ->
            currentFlavor = currentFlavor.replace(variant.buildType.name, "")
       }

        // Getting current application id that are defined in the productFlavors
        String currentApplicationId;
        android.applicationVariants.all { variant ->
            if (variant.flavorName == currentFlavor) {
                currentApplicationId = variant.productFlavors.applicationId.toString().replace("[", "").replace("]", "")
            }
        }

       updateGoogleServicesJsonFile(currentApplicationId)
    }
}
nah0y
  • 141
  • 2
  • 6
2

Answer updated

First of all I must explain I'm using Jenkins to compile my application, so the build process is not exactly the same than in Android Studio. In my case Jenkins only build the release version and is not getting the flavors on the same way than the IDE. I'll explain both solutions:

In the build.gradle (Module: app)

Mine

buildscript{
...
}
android {
...
}

afterEvaluate {
    android.applicationVariants.all { variant ->
        preBuild.doLast {
            setGoogleServicesJson(variant)
        }
    }
    // Only for Jenkins
    assembleRelease.doFirst {
        deleteGoogleServicesJson()
    }
}

def setGoogleServicesJson(variant) {
    def originalFileName = "google-services.bak"
    def newFileName = "google-services.json"
    def originalFile = "./$originalFileName"
    def newFile = "./$newFileName"
    def applicationId = variant.applicationId
    def regularExpression = "\\\"package_name\\\" : \\\"(\\w(\\.\\w)?)+\\\""
    def packageName = "\\\"package_name\\\" : \\\"$applicationId\\\""

    copy {
        from (originalFile)
        into ("./")
        rename (originalFileName, newFileName)
    }
    ant.replaceregexp(
            file: newFile,
            match: regularExpression,
            replace: packageName,
            byLine: true)
}

def deleteGoogleServicesJson() {
    file("./google-services.json").delete()
}

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

Jenkins is getting the google-services.json located in the 'Project/app/' folder and it doesn't use the flavor ones, so for each variant and as soon as possible (after the preBuild task) I'm creating a new JSON from my *.bak file, overriding the package_name and letting Gradle continues with the building.

When everything is done and before it release the app (assembleRelease.doFirst) I delete the google-services.json and I keep the *.bak.

In my case I only want to change the package_name value of my JSON, but this solution won't work if I want to change another value as the project_number, the client_id or whatever else depending on the flavor.

Alternative solution (using flavors)

afterEvaluate {
    android.applicationVariants.all { variant ->
        def fileName = "google-services.json"
        def originalFile = "./$fileName"
        def flavorName = variant.flavorName
        def destinationPath = "."
        // If there is no flavor we use the original path
        if (!flavorName.empty) {
            destinationPath = "$destinationPath/src/$flavorName/"
            copy {
                from file(originalFile)
                into destinationPath
            }
        }
        def regularExpression = "\\\"package_name\\\" : \\\"(\\w(\\.\\w)?)+\\\""
        def packageName = "\\\"package_name\\\" : \\\"$variant.applicationId\\\""
        ant.replaceregexp(
                file: "./$destinationPath/$fileName",
                match: regularExpression,
                replace: packageName, 
                byLine: true)
    }
}

In this solution I have the google-services.json in the 'Project/app/' folder and I make a copy of it in each flavor folder. Then I override the package_name. In case you are working without flavors, the app will use the original JSON to compile.

You can check if another JSON exists in the flavor folder before override it, in case you have different values for the rest of the values.


Old solution

I've found a solution mixing this and this answers.

This is my build.gradle (Module: app) right now:

afterEvaluate {
    android.applicationVariants.all { variant ->
        def applicationId = variant.applicationId
        ant.replaceregexp(file: './google-services.json', match:'package_name_value', replace: applicationId, byLine: true)
    }
}

where package_name_value is the "regular expression" I've defined to be replaced.

The location of the google-services.json is "MyProject/ppp/google-services.json", and I've tested that if you put another googler-services.json inside your flavor folder, it overrides the first one.

*There is (at least) one problem when you have more than one flavor defined at the same time, because this task is always overriding the same file, so the final application id will be the last you have defined.

If you have another way, feel free to post it.

Óscar
  • 1,143
  • 1
  • 19
  • 38
  • 2
    preBuild is obsolete and must be replace by preBuildProvider.get().doLast {...} Thanks for your solution, works lite a charm. –  Oct 10 '20 at 04:37