Create a flavor that extends another flavor using gradle - android

I have an Android application that has two flavors, free and premium. I would like to create another flavor, flavorExt, that is based on the free flavor but has some additional functionality. I have done this using the sourceSets option in gradle, like the following:
sourceSets {
freeExt {
java.srcDirs = sourceSets.free.java.srcDirs
res.srcDirs = sourceSets.free.res.srcDirs
resources.srcDirs = sourceSets.free.resources.srcDirs
}
}
Now I would like to add some additional functionality to the freeExt flavor but I do not know if this is possible and if so, how it can be done. I have created the flavor source folder as app/src/freeExt/java/... and added the necessary files in it. I also added the following line in the sourceSets entry in gradle without success:
java.srcDirs = sourceSets.free.java.srcDirs
Is what I am trying to do possible in gradle? If so, how can it be done?

I would recommend creating a second flavor dimension for the added functionality. You can find the documentation on multiple flavor dimensions here.
Your build.gradle would look something like this:
android {
flavorDimensions "tier", "extras"
productFlavors {
free {
dimension "tier"
...
}
full {
dimension "tier"
...
}
withExtras {
dimension "extras"
...
}
noExtras {
dimension "extras"
...
}
}
}
The Android Gradle Plugin will generate variants using the cross-product of each flavor in each dimension. Thus you will end up with a freeWithExtras, freeNoExtras, fullWithExtras and fullNoExtras. You don't necessarily need to release all four variants.

Related

Android: Are there any methods to share a resource betwwen different falvors and sourceset?

I have an application with below source sets and flavors:
flavorDimensions "brand"
productFlavors {
flavor1 {
dimension "brand"
sourceSets {
main {
manifest.srcFile "src/main/flavor1/AndroidManifest.xml"
resources.srcDirs = ['src/flavor1/']
}
}
}
flavor2 {
dimension "brand"
sourceSets {
main {
manifest.srcFile "src/main/flavor2/AndroidManifest.xml"
resources.srcDirs = ['src/flavor2/']
}
}
}
flavor3 {
dimension "brand"
sourceSets {
main {
manifest.srcFile "src/main/flavor3/AndroidManifest.xml"
resources.srcDirs = ['src/flavor3/']
}
}
}
My application is such that 99% of layouts and classes are the same. E.g. only layout file main_activity.xml is different among flavors but the other 20~30 layout files are completely the same. I don't want to copy these 20~30 in three different flavor source set. Are there any ways to share them among three flavors and if a file was present in source set, it overrides the share files? (Such as want android does for drawable and drawble-hdpi , ...)?
So I don't want to have
Unless I have misunderstood the question, you could solve the issue by changing build.gradle like this:
productFlavors {
flavor1 {
dimension "brand"
}
flavor2 {
dimension "brand"
}
//...
}
and structuring your app module like this:
/src
/main
/flavor1
/flavor2
...
and placing all common files(source, resources) in main and the custom/specific files in the flavorX folders which will cause gradle to replace the identically named files in main when each flavor is built.

Product flavors in Android Studio [duplicate]

This question already has answers here:
Reference different assets based on build flavor
(2 answers)
Closed 3 years ago.
I can not get product flavours working. I have this gradle
apply plugin: 'com.android.application'
android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 24
compileSdkVersion 27
}
signingConfigs {
release {
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
signingConfig signingConfigs.release
}
}
repositories {
maven { url "https://jitpack.io" }
}
flavorDimensions "dim1", "dim2", "dim3"
productFlavors {
flavor1 {
dimension "dim1"
applicationId "com.example.dim1.app"
}
flavor3 {
dimension "dim2"
applicationId "com.example.dim2.app"
}
flavor3 {
dimension "dim3"
applicationId "com.example.dim3.app"
}
}
sourceSets {
flavor1 {
java.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/java/"]
manifest.srcFile "W:/android-studio-projects/sharedid/app/src/example1/AndroidManifest.xml"
assets.srcDirs = ["W:/android-studio-projects/sharedid/app/src/example1/assets/"]
resources.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/res/", "W:/android-studio-projects/sharedid/app/src/example1/res/"]
}
flavor2 {
java.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/java/"]
manifest.srcFile "W:/android-studio-projects/sharedid/app/src/example2/AndroidManifest.xml"
assets.srcDirs = ["W:/android-studio-projects/sharedid/app/src/example2/assets/"]
resources.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/res/", "W:/android-studio-projects/sharedid/app/src/example2/res/"]
}
flavor3 {
java.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/java/"]
manifest.srcFile "W:/android-studio-projects/sharedid/app/src/example3/AndroidManifest.xml"
assets.srcDirs = ["W:/android-studio-projects/sharedid/app/src/example3/assets/"]
resources.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/res/", "W:/android-studio-projects/sharedid/app/src/example3/res/"]
}
}
}
dependencies {
api 'com.google.android.gms:play-services-maps:15.0.0'
api 'com.google.android.gms:play-services-location:15.0.0'
api 'com.android.support:appcompat-v7:27.1.1'
api 'com.github.PhilJay:MPAndroidChart:v2.0.8'
}
...
When I got to "Build | Select variant" I can only select
Module:app
Build Variant:flavor1Flavor2Flavor3Debug,flavor1Flavor2Flavor3Release
I would have liked to get
the following build variants: flavor1Debug,flavor2Debug,flavor3Debug,flavor1Release,flavor2Release,flavor3Release
I have tried "File | Sync project with gradle file"
...
I get this error
Caused by: java.lang.RuntimeException: Cannot read packageName from
W:\android-studio-projects\sharedid\app\src\main\AndroidManifest.xml
I have tried to both
have no such file (hoping it would take the product flavor one?)
have the "main" manifest only define shared stuff between all product flavors
Just try like below,
flavorDimensions "dim1"
productFlavors {
flavor1 {
dimension "dim1"
applicationId "com.example.dim1.app"
}
flavor3 {
dimension "dim1"
applicationId "com.example.dim2.app"
}
flavor3 {
dimension "dim1"
applicationId "com.example.dim3.app"
}
}
For more details about build variant see this link
I think there are two unrelated problems :
Currently you have 2 build types (the automatically created debug and release) and 3 dimensions (dim1, dim2 and dim3), each one having 1 variant (flavor1 for dim1, flavor2 for dim2, ...)
this gives at most :
2 * 1 * 1 * 1 = 2 combinations
You should switch to 2 build types and 1 dimension (say dim1) having 3 variants (flavor1, flaver2 and flavor3) to have :
2 * 3 = 6 apks
You should have a main manifest. Unlike other resources the manifest is not simply overriden but merged from multiple sources (see Merge Multiple Manifest Files for more details).
It should at least contains a package name (possibly different from the final applicationId(s)) as explained by this note from Configure Product Flavors :
Note : You still need to specify a package name using the package attribute
in the main/ manifest file. You must also use that package name in
your source code to refer to the R class, or resolve any relative
activity or service registration. This allows you to use applicationId
to give each product flavor a unique ID for packaging and
distribution, without having to change your source code.
I would have liked to get
the following build variants:
flavor1Debug,flavor2Debug,flavor3Debug,flavor1Release,flavor2Release,flavor3Release
For this, you need to define the same dimension for all flavors.
I get this error
Caused by: java.lang.RuntimeException: Cannot read packageName from
W:\android-studio-projects\sharedid\app\src\main\AndroidManifest.xml
You get this error because the path is not reachable.
Just think, how can app find W: when it is running?
So, you need to use a relative path here.
Also from official documentation (https://developer.android.com/studio/build/build-variants#configure-sourcesets):
If you have sources that are not organized into the default source set
file structure that Gradle expects, as described above in the section
about creating source sets, you can use the sourceSets block to change
where Gradle looks to gather files for each component of a source set.
You don't need to relocate the files; you only need to provide Gradle
with the path(s), relative to the module-level build.gradle file,
where Gradle should expect to find files for each source set component

Gradle SourceSets by productFlavor and buildType

EDIT flavors and paths:
Currently I have:
sourceSets.whenObjectAdded {
sourceSet ->
def sourceData = rootProject.ext[sourceSet.name]
sourceSet.java.srcDirs = sourceData.javaDirRelease
}
The rootProject.ext is a file where all the productFlavor specific configuration is defined like this:
ext{
flavor1 = [
javaDirRelease : ['src/pathToJavaReleaseFiles']
javaDirDebug : ['src/pathToJavaDebugFiles']
]
}
In the main build.gradle I also do: apply from: 'variants.gradle' which contains the above ext{} object.
The sourceSets are defined as such:
sourceSets {
flavor1{}
}
This works but I want to do add a sourceSet specific to productFlavor and the buildType like this:
sourceSet.debug.java.srcDirs = 'src/pathToJavaDebugFiles'
Which could be defined for each product flavor and per buildType, but this doesn't work when I try to add it dynamically.
What works for me is this (thanks to this answer How can I specify per flavor buildType sourceSets?):
sourceSets {
flavor1{
def flavorData = rootProject.ext['flavor1']
release {
java.srcDirs = flavorData.javaDirRelease
}
debug {
java.srcDirs = flavorData.javaDirDebug
}
}
}
However I would really like this to be added dynamically, so I can still preserve my configuration file intact. My build configuration is quite complex and not as simple as described here, therefore I don't need a suggestion to put the source files into a folder src/flavor1Debug because this resources are used from other productFlavors also, so this won't work.
I think I understand. I will say first off once again that this is not a great setup and you should really look to move towards a more standard setup.
So for the solution, in order to read flavor sources dynamically, I recommend moving your ext structure to something like this:
ext {
sources = [
flavor1Debug : ['src/pathToJavaReleaseFiles']
flavor1Release : ['src/pathToJavaDebugFiles']
]
}
This lets you read the source paths and iterate over keys and values in project.ext.sources.
Then you can find the variant by name and add sources from the array.
applicationVariants.all { variant ->
def extraSources = project.ext.sources.get(variant.name)
if (extraSources != null) {
def extraSourceFiles = extraSources.collect { project.file(it) }
def sourceSet = variant.sourceSets.find { it.name == variant.name }
sourceSet.java.srcDir extraSourceFiles
def dummyTask = project.task("register${variant.name}ExtraSourcesTask")
variant.registerJavaGeneratingTask(dummyTask, extraSourceFiles)
}
}
The annoyance with doing this at the variant level is that you have to register the extra sources since the variant object at this point has already collected the sources from the source set and product flavors. However, I think this is probably the least invasive way to do this dynamically.

Android Gradle identify current falvor at compile time

How is it possible to identify the current flavor being compiled. I'm trying to add a file to compile only if I'm compiling a certain product flavor.
buildTypes {
android.applicationVariants.all { variant ->
variant.productFlavors.each() { flavor ->
if (flavor.name.equals(currentFlavorName)) {
The problem is that I can't seem to find where the currentFlavourName of the flavor which I am currently building is located.
just put the strings you want for flavor1 into:
src/flavor1/res/values/strings.xml
and the strings for flavor2 into:
src/flavor2/res/values/strings.xml
no need to put logic into your gradle file
Android uses a unique build process regarding your resources for different flavors and it is very easy to control.
if you set up your main source:
project-name
------------/app
---------------/src
-------------------/main
------------------------/res
----------------------------/values
------------------------/java
-------------------/development
-------------------------------/res
-----------------------------------/values
-------------------------------/java
-------------------/production
------------------------------/res
----------------------------------/values
------------------------------/java
This would be a bottom up approach from product flavor into main. Meaning if you have a strings.xml with items having the same name existing in development/res/values and have values that also exist in main/res/values/strings.xml these will be over written (and same would go for the production flavor) based on the build variant defined in your gradle file.
android {
productFlavors {
production {
applicationId "com.test.prod"
versionName "1.0.0"
}
development {
applicationId "com.test.dev"
versionName "1.0.0"
}
}
I don't know if exits a method to get the currentFlavor. I haven't found it yet.
A ugly solution can be
variant.productFlavors.each() { flavor ->
if (flavor.name.equals("flavor1")) {
//..........
}
}
However, if you want to be able to control which strings.xml you are using, you can achieve it in different ways.
First of all you can just define a xml file in your flavor folder.
app/src/main/res/values/ -> for common resources
app/src/flavor1/res/values -> resources for flavor1
app/src/flavor2/res/values -> resources for flavor2
This doesn't require any config in your build.gradle script.
A second option is to define a resource value using build.gradle.
Something like:
productFlavors {
flavor1 {
resValue "string", "app_name", "IRCEnterprise"
}
//....
}
Another option is to create some field in your BuildConfig class using this kind of script.
productFlavors {
flavor1 {
buildConfigField "String", "name", "\"MY FLAVOR NAME\""
}
}

Custom build Android app from command line

I'm trying to make a batch file with some commands, so once it runs, it will change some strings in the files, build the project and generate the APK signed.
The strings I need to change are:
- The package name (com.company.project)
- Some images (like icons, splash screen, ...)
- some irrelevant string that are specific from the app.
For the last 2 things I know how to do it, but for the package name I feel there is something wrong about just find and replace all the occurrences of that string in the root folder of the app (including subdirectories).
Is there any way or command that ant has for doing this?
Also I ran into an issue while running the command ant release.
I went to my root folder, ran the command and it gets errors.
So I had to go to eclipse, clean the project and let it autobuild (with no generation of APK since it does that when you try to run it on a device) so at that point my bin folder just contains the folders: classes, dexedLibs, res and the Manifest.xml file.
Then I can go to the CL and run ant release.
So is there any way to do all this from CL? Something like clean and build so I can run ant release command after with no issues?
NOTE: for find and replace I use an .exe called FNR that does the job
EDIT:
I'm now using gradle and can build changing the package name but there is still a few things I want to do in the build.gradle file and can't make it work.
This is build.gradle:
task("hello"){
println "Hello world!!"
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.+'
}
}
apply plugin: 'android'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
productFlavors {
flavor1 {
packageName "com.testCompany.testProject"
}
}
signingConfigs {
release {
storeFile file("keystore/android.keystore")
storePassword 'blah blah'
keyAlias "blah blah"
keyPassword 'blah blah blah'
}
}
buildTypes {
flavor1 {
zipAlign true
sourceSets {
main {
signingConfig signingConfigs.release
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
}
}
}
I am pretty sure I'm doing things wrong.
So I want to:
- Change some strings in the res/strings.xml file.
- Change the icon/png files in the res/drawable.... folder for custom ones.
I have completely no idea of how to do it. I tried:
buildTypes{
flavor1{
copy{
from('src/res/'){
include '**/*.xml'
filter{String line -> line.replaceAll(string_to_be_replaced, replaced_string)}
}
into '$buildDir/res'
}
}
}
but nothing
The strings I need to change are: - The package name (com.company.project)
If you are changing these things based upon whether this is a debug build or a release build, you can specify a suffix on the package name for a build type:
android {
buildTypes {
debug {
packageNameSuffix ".debug"
}
}
}
Or, if you are changing these things for anything else, you can create product flavors and replace the package name per flavor:
android {
productFlavors {
flavor1 {
packageName "com.example.flavor1"
}
flavor2 {
packageName "com.example.flavor2"
}
}
}
Some images (like icons, splash screen, ...) - some irrelevant string that are specific from the app.
You can create source sets for build types (e.g., src/debug/) or product flavors (e.g., src/flavor1/) and have replacement versions of resources in them. That will handle your images, as well as your "irrelevant string" if you define it as a string resource.
Source sets can also have Java code, though that gets incrementally more complex, so I would recommend that you use the string resources and replacement resources instead.

Categories

Resources