I have an application with 2 product flavors and as a result 4 build variants:
1) freeDebug
2) freeRelease
3) paidDebug
4) paidRelease
I have a Fragment that contains an ad, and it takes 2 lines to implement:
#Nullable #BindView (R.id.bottomsheet_ad) AdView mAdView;
mAdView.loadAd(adRequest);
So because of 2 lines, I have to potentially maintain 2 or more files.
I'm considering 2 solutions:
1) Check flavor at runtime:
if (BuildConfig.FLAVOR.equals("flavor123")) {
...
}
2) Create a common flavor and point needed variants in gradle, as explained here:
android {
...
productFlavors {
flavorOne {
...
}
flavorTwo {
...
}
flavorThree {
...
}
flavorFour {
...
}
}
sourceSets {
flavorOne.java.srcDir 'src/common/java'
flavorTwo.java.srcDir 'src/common/java'
flavorThree.java.srcDir 'src/common/java'
}
}
Which solution would be better? And is checking flavor at runtime as above considered polluting the code?
You can add something like following to appropriate flavor in your build.gradle
buildConfigField "boolean", "showAds", 'true'
And then use like following (your main src files will still be used for additional flavors you add):
if (BuildConfig.showAds) {
}
Related
I was following this article ("Modularizing your flavored Android project") by Piotr Zawadzki to add flavors to multiple modules of my project but I got stuck when trying to convert the file flavors.gradle from Groovy to Kts (which is what I am currently using).
// flavors.gradle
ext.flavorConfig = { // 1
flavorDimensions "pricing"
productFlavors {
free {
dimension "pricing"
ext.myApplicationIdSuffix = '.free' // 2
}
paid {
dimension "pricing"
ext.myApplicationIdSuffix = '.paid'
}
}
productFlavors.all { flavor -> // 3
if (flavor.hasProperty('myApplicationIdSuffix') && isApplicationProject()) {
flavor.applicationIdSuffix = flavor.myApplicationIdSuffix
}
}
}
def isApplicationProject() { // 4
return project.android.class.simpleName.startsWith('BaseAppModuleExtension')
// in AGP 3.1.x with library modules instead of feature modules:
// return project.android instanceof com.android.build.gradle.AppExtension
}
Is it possible to convert it to Kts?
I would like to launch my Android app in such a way that I can set some external variable that my app can read. It would be nice if this was possible either in Gradle or as part of the debug/run configuration.
In essence, I would like to test for a variable to see if it is set. In this example I would like to set USE_FAKE_DATA:
if (USE_FAKE_DATA) {
...
} else {
...
}
One way is to use build variants and I have done this before. But I'm wondering if another way has been made available.
Gradle File
android {
buildTypes {
debug {
buildConfigField "boolean", "USE_FAKE_DATA", "true"
}
release {
buildConfigField "boolean", "USE_FAKE_DATA", "false"
}
}
}
Java File
class Test extends Activity {
#Override
public void onCreate(Bundle data) {
if (BuildConfig.USE_FAKE_DATA) {
...
} else {
...
}
}
}
Please refer this answer for more.
My project has several Product Flavors which all of them share the same native library except for one that uses slightly different one with the same name.
Consider this is the build.gradle:
android {
...
productFlavors {
p0 {
....
}
p1 {
....
}
p2 {
....
}
p3 {
....
}
p4 {
....
}
p5 {
....
}
}
}
and this is folder structure:
src
...main
......jnilibs
.........armeabi-v7a
............lib1.so
...p5
......jnilibs
.........armeabi-v7a
............lib1.so
When I compile the p5 flavour it complains that lib1.so is duplicate (Error:duplicate files during packaging of APK) and it exists in multiple sources. It suggest to exclude this file, although I don't want to exclude but adding suggested code doesn't change anything. I know I can create different folders for each of the other flavors and put the same lib1.so in each jniLibs of them, but it's really not an option.
For those who want to accomplish the same thing, best I found so far is to change the root of source set of packages which share the same library.
the build.gradle would be like this:
android {
...
productFlavors {
p0 {
....
}
p1 {
....
}
p2 {
....
}
p3 {
....
}
p4 {
....
}
p5 {
....
}
}
sourceSets {
p0.setRoot("src/p")
p1.setRoot("src/p")
p2.setRoot("src/p")
p3.setRoot("src/p")
p4.setRoot("src/p")
}
}
Folder structure:
src
...main
...p
......jnilibs
.........armeabi-v7a
............lib1.so
...p5
......jnilibs
.........armeabi-v7a
............lib1.so
I have a relatively complicated project that requires two flavor dimensions for each app. I've rewritten it much more simply in the example below:
flavorDimensions "shape", "color"
productFlavors {
blue {
flavorDimension "color"
}
red {
flavorDimension "color"
}
green {
flavorDimension "color"
}
square {
flavorDimension "shape"
}
circle {
flavorDimension "shape"
}
I want to be able to set a different applicationId for each variant, eg: squareblue would have a different applicationId to circleblue. I can't set the applicationId in the color dimension because it would be the same for each shape. I would need to have 6 different applicationIds in the above example. These Ids also don't follow any pattern, they could be anything.
I've seen the answer here: How to set different applicationId for each flavor combination using flavorDimensions? but that would mean I need to set it up manually, which isn't feasible for my project, due to the number of variants (1000s).
What I really want to do is set two applicationids on the color dimension, then it picks the correct one, depending on the shape dimension, when it's built. I've tried defining variables but haven't had any success with that, they just get overwritten by the last variant.
Gradle has an extras property built in, so you could do this without defining a class.
Would look something like this, might have made a typo or two:
productFlavors {
blue {
flavorDimension "color"
ext.squareId = "yourAppId"
ext.circleId = "yourAppId"
}
android.applicationVariants.all { variant ->
def flavors = variant.getFlavors()
if (flavors[0].name.equals("square")){
variant.mergedFlavor.setApplicationId(flavors[1].ext.squareId)
} ...
}
I found a solution following from this example here: Gradle Android Plugin - add custom flavor attribute?
If you add a class like this to your gradle file you can add custom attributes to a productFlavor
class AppIdExtension {
String squareId
String circleId
AppIdExtension(String sId, String cId){
squareId = sId
circleId = cId
}
public void setSquareId(String id){
squareId = id
}
public String getSquareId(){
squareId
}
public void setCircleId(String id){
circleId = id
}
public String getCircleId(){
circleId
}
}
You then add this to extension to each flavor by adding the following code at the start of your android { } section
productFlavors.whenObjectAdded { flavor ->
flavor.extensions.create("shapeIds", AppIdExtension, "", "")
}
Then inside your product flavor you can set the values for each shape type
blue {
flavorDimension "color"
platformIds.squareId "yourAppId"
platformIds.circleId "yourAppId"
}
Then finally after your productFlavors { } section you add the following:
android.variantFilter { variant ->
def applicationId = ""
def flavors = variant.getFlavors()
if(flavors[0].name.equals("square")){
applicationId = flavors[1].platformIds.squareId
} else if(flavors[0].name.equals("circle")){
applicationId = flavors[1].platformIds.circleId
}
variant.getDefaultConfig().applicationId applicationId
}
This may not be the most elegant or efficient way of achieving it, but it is working perfectly for me. Now I can add all of my Ids in the productFlavor section and then the variantFilter sets the correct applicationId depending on the first flavor.
def appId = "my.custom.package"
if (appId == "some.package") {
..... Change conditions based on the flavors maybe defined more variables
}
defaultConfig {
applicationId "${appId}"
...............
}
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.