Reputation: 63159
I've created an Android App which needs to be build in many (30+) flavors.
My idea was to generate the different productFlavors
directly from the folder structure in my src
directory, since the configuration is always very similar (basically just another packageName, a new launcher icon and some strings change).
The src
folder look like this:
└── src
├── flavor1
│ ├── flavor2.keystore
│ ├── res
├── flavor2
│ ├── res
│ ├── flavor2.keystore
└── main
├── AndroidManifest.xml
├── java
└── res
If I had to create the gradle properties by hand it would look somehow like this:
android {
....
productFlavors {
flavor1 {
packageName 'com.example.flavor1'
}
flavor2 {
packageName 'com.example.flavor2'
}
}
}
Everytime I try to change the productFlavors
configuration after its creation I get either an error or the changes / additions are ignored silently.
This could a be problem caused by me, because my Gradle / Groovy experience is very limited, or this isn't possible.
I mostly get error, saying that GroupableProductFlavorDsl_Decorated
could not be manipulated the way I want.
What I'm trying to archive should somehow look like this:
android {
....
def flavors = getMyFlavorsFromFileSystem()
productFlavors {
}
flavors.each { name, config ->
productFlavors[name] << config
}
}
Note: I know this question is basically an duplicate of an older question, which sadly was never answered. Since Gradle is kind of new to the Android world, I'm hoping to get more answers as since the last time the question was asked, because more developers are using Gradle now.
Update:
Here some very simple approaches I tried:
Variant 1:
android {
productFlavors {
}
productFlavors['flavor1'] << {
packageName "com.example.flavor1"
}
productFlavors['flavor2'] << {
packageName "com.example.flavor2"
}
}
/*
A problem occurred evaluating root project 'MyProject'.
> GroupableProductFlavorDsl with name 'flavor1' not found.
*/
Variant 2:
android {
productFlavors {
}
productFlavors['flavor1'] = {
packageName "com.example.flavor1"
}
productFlavors['flavor2'] = {
packageName "com.example.flavor2"
}
}
/*
no error, but does not work
*/
Variant 3:
android {
productFlavors {
}
productFlavors['flavor1'] = [packageName: "com.example.flavor1"]
productFlavors['flavor2'] = [packageName: "com.example.flavor2"]
}
/*
no error, but does not work
*/
All of them as a Gist.
Upvotes: 20
Views: 6825
Reputation: 12539
This answer is very late but may be useful to someone. We have 200+ flavors and we manage them by only one .json file with dynamic flavors.
projects_info.json
file inside projects_info folder and insert information for every flavor.src
folder and put respected code/resources.{
"projectsInfoJSONArray": [
{
"projectVariantName": "project1",
"versionCode": 1,
"versionName": "1.0",
"applicationId": "com.project1",
"storeFile": "keystores/MyKeyStore1.jks",
"storePassword": "yourStorePassword",
"keyAlias": "yourKeyAlias",
"keyPassword": "yourKeyPassword",
"isLive": 1
},
{
"projectVariantName": "project2",
"versionCode": 2,
"versionName": "1.2",
"applicationId": "com.project2",
"storeFile": "keystores/MyKeyStore2.jks",
"storePassword": "yourStorePassword",
"keyAlias": "yourKeyAlias",
"keyPassword": "yourKeyPassword",
"isLive": 1
}
]
}
build.gradle
file. And sync project with gradle.def applicationDefinitions = []
def projectsInfoFile = file('projects_info/projects_info.json')
def projectsInfoJSON = new JsonSlurper().parseText(projectsInfoFile.text)
def projectsInfoArray = projectsInfoJSON.projectsInfoJSONArray
def projectsInfoMap = [:]
projectsInfoArray.each { projectInfo ->
projectsInfoMap[projectInfo.projectVariantName] = projectInfo
applicationDefinitions.add(['name': projectInfo.projectVariantName, 'applicationId': projectInfo.applicationId])
}
applicationDefinitions.each { applicationDefinition ->
def signingConfig = projectsInfoMap[applicationDefinition['name']]
if (signingConfig.isLive == 1) android.productFlavors.create(applicationDefinition['name'], { flavor ->
flavor.applicationId = applicationDefinition['applicationId']
flavor.versionCode = signingConfig.versionCode
flavor.versionName = signingConfig.versionName
flavor.signingConfig = android.signingConfigs.create(applicationDefinition['name'])
flavor.signingConfig.storeFile = file(signingConfig.storeFile)
flavor.signingConfig.storePassword = signingConfig.storePassword
flavor.signingConfig.keyAlias = signingConfig.keyAlias
flavor.signingConfig.keyPassword = signingConfig.keyPassword
}) else println "===> " + signingConfig.projectVariantName + " is Not LIVE. Excluding it from build."
}
Upvotes: 1
Reputation: 503
I know there's already an answer for this, but I kind of combined Chris.Zou's approach with TheHippo. And add my own method for doing this.
Basically, when dealing with flavors, we normally work with different directories under /app/src/ which contains resources. And since the directory name is equal to the package name, I simply listed the directories under that folder (excluded "main" and "androidTest").
So here's my complete, working gradle script:
def map = [:]
new File("./app/src").eachFile() { file->
def fileName = file.getName()
if( fileName == "main" || fileName == "androidTest") {
return
}
map.put(fileName, 'com.mypackagename.' + fileName )
}
productFlavors {
map.each { flavorName, packagename ->
"$flavorName" {
applicationId packagename
}
}
}
Edit:
Upvotes: 8
Reputation: 4586
Since the question author didn't want to share his code for reading files. I'm gonna write about what I did. I put all the variants name in a file named "app/build_variants.txt", one line for each, something like this:
flavor1
flavor2
flavor3
flavor4
flavor5
And in "app/build.gradle":
//...other configs...
android {
productFlavors {
new File('app/build_variants.txt').eachLine { "$it" { resValue "string", "build_variant_name", "$it" } }
}
}
//...other configs
This will have the same result as the following code:
//...other configs...
android {
productFlavors {
flavor1 { resValue "string", "build_variant_name", "flavor1" }
flavor2 { resValue "string", "build_variant_name", "flavor2" }
flavor3 { resValue "string", "build_variant_name", "flavor3" }
flavor4 { resValue "string", "build_variant_name", "flavor4" }
flavor5 { resValue "string", "build_variant_name", "flavor5" }
}
}
//...other configs
The key here is the line new File('app/build_variants.txt').eachLine { "$it" { resValue "string", "build_variant_name", "$it" } }
. It reads the file 'app/build_variants.txt' and for each line, generate a product flavor with the name in the line it reads. the $it
is the line it passes in. It just groovy's closure syntax. If you want a deeper understanding. I strongly recommend watching @Daniel Lew's video about groovy and gradle. It's awesome!.
Upvotes: 4
Reputation: 63159
Solved by trial and error:
android {
// let's assume these are return by a function which reads the filesystem
def myFlavors = [
flavor1: [
packageName: "com.example.flavor1"
],
flavor2: [
packageName: "com.example.flavor2"
]
]
productFlavors {
myFlavors.each { name, config ->
"$name" {
packageName config.packageName
}
}
}
}
Upvotes: 20