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.
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)
}
}
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.
Related
I am trying to generate some Android resources in a Gradle task.
I've written a task which parses an input file, and writes out an XML file to a location under the app's build directory.
app/build.gradle
import groovy.xml.MarkupBuilder
task generateSomeAppResources {
ext.outputDir = new File(projectDir, "build/generated/res/values")
doFirst {
mkdir outputDir
new File(outputDir, "generated.xml").withWriter { writer ->
def destXml = new MarkupBuilder(new IndentPrinter(writer, " ", true, true))
destXml.setDoubleQuotes(true)
def destXmlMkp = destXml.getMkp()
destXmlMkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
destXmlMkp.comment("Generated at ${new Date()}")
destXmlMkp.yield "\r\n"
destXml.resources() {
"string"("name": "generated_app_resource") {
destXmlMkp.yield("Some generated value for the app")
}
}
}
}
}
This works fine, and the generated output looks like I expect.
generated.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated at Wed Feb 12 12:46:12 GMT 2020 -->
<resources>
<string name="generated_app_resource">Some generated value for the app</string>
</resources>
I am struggling to get the Android build system to detect the generated file, though. Google's advice is
to write a task that outputs a generated resource directory structure with whatever you need, use BaseVariant.registerGeneratedResFolders()
But documentation on registerGeneratedResFolders() is non-existent. After much tedious searching I found some example usages in the Play Services Plugin source, for example, so I tried to add something along those lines.
app/build.gradle
android.applicationVariants.all { variant ->
def files = project.files(generateSomeAppResources.outputDir)
files.builtBy(generateSomeAppResources)
variant.preBuildProvider.configure { dependsOn(generateSomeAppResources) }
variant.mergeResourcesProvider.configure { dependsOn(generateSomeAppResources) }
variant.registerGeneratedResFolders(files)
}
But I'm missing something. The generated resource shows up purple in Android Studio, meaning that the IDE thinks it exists...
...but the code fails to compile with an Unresolved reference: generated_app_resource error.
I don't know what magic incantations are needed to make the Android build system pick up these resources. How do I get this to build?
To create resources, android requires
1) A resource directory above values folder then you can add desired resources as per your requirement
2) Instruct the build process to add the generated resources while building R.java
So first configure your build resource task like:
task generateSomeAppResources {
ext.outputDir = new File(projectDir, "build/generated/res/custom/values")
print("path is "+projectDir)
doFirst {
mkdir outputDir
new File(outputDir, "strings.xml").withWriter { writer ->
def destXml = new MarkupBuilder(new IndentPrinter(writer, " ", true, true))
destXml.setDoubleQuotes(true)
def destXmlMkp = destXml.getMkp()
destXmlMkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
destXmlMkp.comment("Generated at ${new Date()}")
destXmlMkp.yield "\r\n"
destXml.resources() {
"string"("name": "generated_app_resource") {
destXmlMkp.yield("Some generated value for the app")
}
}
}
}
}
now add the path in the build process using sourceSets in build.gradle(app) like
android {
//....
sourceSets {
main {
res.srcDirs += [
'build/generated/res/custom',
]
}
}
}
Additionally, add the task in the current build process as
gradle.projectsEvaluated {
preBuild.dependsOn('generateSomeAppResources')
} // no need of `android.applicationVariants.all...`
Now sync the project and it will work as expected.
Result:
Example on how to generate resources using BaseVariant.registerGeneratedResFolders
def generateResourcesTask = tasks.register("generateResources", GenerateResourcesTask)
android.libraryVariants.all { variant ->
variant.registerGeneratedResFolders(
project.files(generateResourcesTask.map { it.outputDir })
)
}
Using tasks.register(...), project.files(...) and TaskProvider.map(...) utilizes task avoidance api, and automatically wires up tasks dependencies. registerGeneratedResFolders also automatically adds resources to sourceset, so that you can reference generated resources in your code.
abstract class GenerateResourcesTask : DefaultTask() {
// If you need inputs
// #get:InputFiles
// lateinit var inputs: FileCollection
#get:OutputDirectory
val outputDir = File(project.buildDir, "generated/res/regenerate/")
private val outputFile = File(outputDir, "values/missingRes.xml")
#TaskAction
fun action() {
val result = buildString {
appendln(
"""
<?xml version="1.0" encoding="utf-8"?>
<resources>
""".trimIndent()
)
appendln(""" <string name="test_string">Test string</string>""")
appendln("</resources>")
}
outputFile.parentFile.mkdirs()
outputFile.writeText(result)
}
}
You can define this task class in build.gradle file, or in buildSrc.
Using annotations like #OutputFile makes your task incremental.
For more info no what annotations do and how to use them check out gradle's documentation. You can also use Runtime API to create tasks with properties.
I am trying to rename the generated .aab file in format <appname>-<variant>-<buildType>-<version>, is there any way output bundle file can be renamed similar to apk renaming.
Copying and renaming I have tried, but the problem there is I need to name the bundle as <appname>-<variant>-<buildType>-<version>. when I create the task as mentioned here How to change the generated filename for App Bundles with Gradle?
getting the variant is using a substring, which gives both variantBuildtype.
First I'd like to clarify what a build variant is.
A build variant is a combination of the build type and flavor.
For example, a build type could be release and a flavor could be something like freeVersion. So a variant would be releaseFreeVersion.
Note: the answer that you linked has a side effect of creating an additional copy of the .aab file rather than only renaming it. This answer also fixes that.
Part 1: This answer assumes you are using product flavors
In this answer I'll assume that you are using product flavors. I'll also assume the format you want is <appname>-<flavor>-<buildType>-<version>.
First create some supplementary Gradle methods
You can add these toward the bottom of your build.gradle file (outside of all curly braces).
static String firstMatchingSubstring(String taskName, String[] keys) {
def lcName = taskName.toLowerCase()
for(String key: keys) {
if(lcName.contains(key.toLowerCase())) return key
}
return null
}
/**
*
* #param taskName e.g., bundleMyFlavorRelease or bundleRelease
* #return
*/
String getBuildType(String taskName) {
return firstMatchingSubstring(taskName, getBuildTypeNames())
}
/**
*
* #param taskName e.g., bundleMyFlavorRelease
* #return
*/
String getFlavor(String taskName) {
return firstMatchingSubstring(taskName, getProductFlavorNames())
}
String[] getBuildTypeNames() {
def types = []
android.buildTypes.all { type ->
types.add(type.name)
}
return types
}
String[] getProductFlavorNames() {
def flavors = []
android.productFlavors.all { flavor ->
flavors.add(flavor.name)
}
return flavors
}
Create a task to rename the app bundle
Similar to the accepted answer you linked in your question we'll insert a rename task.
I hard coded the app name but if you want to have different app names you could put them inside the android.defaultConfig block and then reference the name below.
tasks.whenTaskAdded { task ->
def name = task.name
//Skip some unnecessary tasks
if (name.startsWith("bundle")
&& !name.contains("Classes")
&& !name.contains("Resources")
&& name != "bundle") {
def renameTaskName = "rename${task.name.capitalize()}Aab"
def version = "${android.defaultConfig.versionName}-${android.defaultConfig.versionCode}"
def flavor = getFlavor(name)
def type = getBuildType(name)
if(flavor == null || type == null) return
def outputName = "AppName-$flavor-$type-$version"
tasks.create(renameTaskName) {
def path = "${rootDir}/app/${flavor}/${type}/"
def originalFile = "$path/app-${flavor}-${type}.aab"
doLast {
if (file("$originalFile").exists()) {
ant.move file: "$originalFile",
tofile: "$path/${outputName}.aab"
}
}
}
task.finalizedBy(renameTaskName)
}
}
Part 2: This answer assumes you are not using product flavors
If you are not using product flavors then something more simple like the accepted answer you linked in your question with some modifications. This would produce a name like <appname>-<buildType>-<version>.aab.
tasks.whenTaskAdded { task ->
def name = task.name
//Skip some unnecessary tasks
if (name.startsWith("bundle")
&& !name.contains("Classes")
&& !name.contains("Resources")
&& name != "bundle") {
def renameTaskName = "rename${task.name.capitalize()}Aab"
def type = task.name.substring("bundle".length()).uncapitalize()
def version = "${android.defaultConfig.versionName}-${android.defaultConfig.versionCode}"
def outputName = "AppName-$type-$version"
tasks.create(renameTaskName) {
def path = "${rootDir}/app/${type}/"
def originalFile = "$path/app-${type}.aab"
doLast {
if (file("$originalFile").exists()) {
ant.move file: "$originalFile",
tofile: "$path/${outputName}.aab"
}
}
}
task.finalizedBy(renameTaskName)
}
}
As per https://developer.android.com/studio/write/lint.html#snapshot we can create a Lint warning baseline file.
The problem is that I have multiple flavors, each having their own sourceSets. Some files are used in a single flavor.
When I generate the baseline file, it's always specific to a variant. Which means that it's invalid for the other variants, ie it will miss some existing issues.
I have tried putting the
lintOptions {
baseline file("lint-baseline.xml")
}
in the build and flavor blocks, but it won't generate multiple baselines.
Has anyone managed to generate flavor specific lint baseline file? And if so how?
Thanks!
I was trying the same thing and found a way of doing it.This create diff file for release and debug.You can put your custom logic in getfileName
lintOptions {
baseline file(getFileName())
checkAllWarnings true
warningsAsErrors true
abortOnError true
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().args.toString()
println tskReqStr
if (tskReqStr.contains("Debug")) {
return "debug"
} else {
return "release"
}
}
private String getFileName(String command) {
return getCurrentFlavor() + "-lint-baseline.xml"
}
I couldn't make the above answer exactly work as I got errors when trying to define the method in the build.gradle file.
Using himanshu saluja's answer this is what worked for me.
My root project's gradle file has:
ext.getLintFileName = {
Gradle gradle = getGradle()
String taskReq = gradle.getStartParameter().getTaskRequests().args.toString()
if(taskReq.contains("Debug")) {
return "debug-lint-baseline.xml"
} else {
return "release-lint-baseline.xml"
}
}
And the sub project's gradle file inside the android block uses the value like this:
lintOptions {
baseline file(rootProject.ext.getLintFileName)
checkDependencies true
abortOnError true
absolutePaths false
}
Given that the baseline feature is on LintOptions and this one is AFAIK not capable of being variant aware, this will not work out of the box.
You could file a feature request on https://b.android.com though.
according to my GitHub sample code:
1- add the following function to your app-level build.gradle file:
def getPath() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern
String path
String fileName = "lint-baseline"
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else if (tskReqStr.contains("generate"))
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
else if (tskReqStr.contains("lint"))
pattern = Pattern.compile("lint(\\w+)(Release|Debug)")
if(pattern != null) {
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find()) {
path = matcher.group(1).toLowerCase() + matcher.group(2).toLowerCase()
return "lint-baselines/${path}-${fileName}.xml"
} else {
return "lint-baselines/${fileName}.xml"
}
}
return "lint-baselines/${fileName}.xml"
}
this function creates a specific path for each build variants. you can customize the file name by changing the "fileName" variable.
2- add the following line to lintOption scop of your app-level build.gradle file:
lintOptions {
...
// Use (or create) a baseline file for issues that should not be reported
baseline file("${getPath()}")
...
}
3- run linter for each of build varients.
for example type the following command in the terminal tab of Android studio in the root of project:
gradlew app:lintMyAppRelease
app = your module name
MyAppRelease = your build varient
4- Done
I would like to create dynamic flavors from the directory tree.
It works great!
But Android Studio uses gradle in its tmp file like:
/home/svirch_n/.IntelliJIdea14/system/compile-server
and my script doesn't work anymore because it uses relative paths like this:
Closure getFlavors = { rootDir, basePackage ->
def result = [:]
new File("$rootDir").eachDir() { dir ->
def name = dir.getName()
if ("$name" != "main")
result.put("$name", "$basePackage.$name")
}
return result
}
// This is an ugly closure.
// If I can get rid of this, my problem will be solved
Closure getSrcPath = {
if (System.getProperty("user.dir").split("/").last() == "app") {
return "src"
} else {
return "app/src"
}
}
android {
...
def myFlavors = getFlavors(getSrcPath(), "com.example.app")
productFlavors {
myFlavors.each { flavorName, flavorPackage ->
"$flavorName" {
applicationId "$flavorPackage"
}
}
}
}
Do you have an idea how to solve this?
Thanks in advance for your help
P.S: I want dynamic flavors cause my git project has public and private repositories and not everyone can have all the flavors but I want them to compile anyway.
Assuming I am in the subproject 'app', I can use:
project(":app").getProjectDir().getPath()
I have two flavors of my project:
flavor1 -> packagename: com.example.flavor1
flavor2 -> packagename: com.example.flavor2
Now I want to build a buildvariant of flavor1 and flavor2.
The only difference of the buildvariant is another packagename.
My project uses MapFragments and has just one Manifest - so I put the the permission name of MAPS_RECEIVE in my string resource files of the respective flavors.
The question is: how can I replace a string resource of a buildvariant?
I tried the following approach (described in this post):
buildTypes{
flavor1Rev{
packageName 'com.example.rev.flavor1'
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: ['package_permission' : 'com.example.rev.flavor1.permission.MAPS_RECEIVE'])
}
}
But using this I got this error:
Could not find method filter() for arguments
[{tokens={package_permission=com.example.rev.flavor1.permission.MAPS_RECEIVE}},
BuildTypeDsl_D ecorated{name=ReplaceTokens, debuggable=false,
jniDebugBuild=false, renderscript DebugBuild=false,
renderscriptOptimLevel=3, packageNameSuffix=null, versionNameS
uffix=null, runProguard=false, zipAlign=true, signingConfig=null}] on
BuildTypeD sl_Decorated{name=buderusFinal, debuggable=false,
jniDebugBuild=false, renderscr iptDebugBuild=false,
renderscriptOptimLevel=3, packageNameSuffix=null, versionNa
meSuffix=null, runProguard=false, zipAlign=true, signingConfig=null}.
Do I have to define an own task for the filter method?
EDIT [2013_07_09]:
String in src/flavor1/res:
<string name="package_permission">package_permission</string>
Code in build.gradle to replace the string:
buildTypes{
flavor1Rev{
copy{
from('src/res/'){
include '**/*.xml'
filter{String line -> line.replaceAll(package_permission, 'com.example.rev.flavor1.permission.MAPS_RECEIVE')}
}
into '$buildDir/res'
}
}
}
I solved the problem on my own, so here is the solution "step by step" - perhaps it will help some other newbies to gradle :)
Copy Task in General:
copy{
from("pathToMyFolder"){
include "my.file"
}
// you have to use a new path for youre modified file
into("pathToFolderWhereToCopyMyNewFile")
}
Replace a line in General:
copy {
...
filter{
String line -> line.replaceAll("<complete line of regular expression>",
"<complete line of modified expression>")
}
}
I think the biggest problem was to get the right paths, because I had to make this dynamically (this link was very helpful for me). I solved my problem by replacing the special lines in the manifest and not in the String-file.
The following example shows how to replace the "meta-data"-tag in the manifest to use youre google-maps-api-key (in my case there are different flavors that use different keys ):
android.applicationVariants.each{ variant ->
variant.processManifest.doLast{
copy{
from("${buildDir}/manifests"){
include "${variant.dirName}/AndroidManifest.xml"
}
into("${buildDir}/manifests/$variant.name")
// define a variable for your key:
def gmaps_key = "<your-key>"
filter{
String line -> line.replaceAll("<meta-data android:name=\"com.google.android.maps.v2.API_KEY\" android:value=\"\"/>",
"<meta-data android:name=\"com.google.android.maps.v2.API_KEY\" android:value=\"" + gmaps_key + "\"/>")
}
// set the path to the modified Manifest:
variant.processResources.manifestFile = file("${buildDir}/manifests/${variant.name}/${variant.dirName}/AndroidManifest.xml")
}
}
}
I use almost exactly the approach you wanted to. The replaceInManfest is also generic and can be used for other placeholders as well. The getGMapsKey() method just returns the appropriate key based on the buildType.
applicationVariants.all { variant ->
def flavor = variant.productFlavors.get(0)
def buildType = variant.buildType
variant.processManifest.doLast {
replaceInManifest(variant,
'GMAPS_KEY',
getGMapsKey(buildType))
}
}
def replaceInManifest(variant, fromString, toString) {
def flavor = variant.productFlavors.get(0)
def buildtype = variant.buildType
def manifestFile = "$buildDir/manifests/${flavor.name}/${buildtype.name}/AndroidManifest.xml"
def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
new File(manifestFile).write(updatedContent, 'UTF-8')
}
I have it up on a gist too if you want to see if it evolves later.
I found this to be a more elegant and generalizable approach than the others (although the token replacement just working would have been nicer).
The answers are quite outdated, there are better ways now to archive it. You can use the command in your build.gradle:
manifestPlaceholders = [
myPlaceholder: "placeholder",
]
and in your manifest:
android:someManifestAttribute="${myPlaceholder}"
more information can be found here:
https://developer.android.com/studio/build/manifest-merge.html
In the current Android Gradle DSL, the ApplicationVariant class has changed and Saad's approach has to be rewritten e.g. as follows:
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.processManifest.doLast {
replaceInManifest(output,
'GMAPS_KEY',
getGmapsKey(buildType))
}
}
}
def replaceInManifest(output, fromString, toString) {
def updatedContent = output.processManifest.manifestOutputFile.getText('UTF-8')
.replaceAll(fromString, toString)
output.processManifest.manifestOutputFile.write(updatedContent, 'UTF-8')
}
The new DSL also offers a cleaner approach to get directly to the manifest file.