Product Flavors for Android Library
Product flavors is a powerful feature of the gradle plugin in Android Studio. Several developers use it to create free/paid or full/demo versions, releasing customized versions of their product.
In this post I would add product flavors to one of the libraries that I am working on. This library is an enterprise entity and different clients have different requirements with respect to minSdkVersion
, targetSdkVersion
, support library versions and other internal dependencies.
Product Flavors on Applications
Using product flavors on a regular applications is easy. Just add the productFlavors block and you are good to go.
app/build.gradle
productFlavors {
free {
applicationId "xyz.sahildave.flavoredlibrary.free"
}
paid {
applicationId "xyz.sahildave.flavoredlibrary.paid"
}
}
You can now create sourceSets folders app/src/paid/
and app/src/free/
parallel to app/src/main/
. The common classes and resources can stay in app/src/main
while the flavor dependent code can go in the respective folders. Hold on to this concept, we’d use it later.
Product Flavors in Android Library
Creating the product flavor in the library’s build.gradle
file is basically the same as above, the thing that changes is the dependency management in the app module. Here I create 2 flavors with different sdk versions.
Please note that publishing of all variants are not enabled by default. You need to use the publishNonDefault
variable in the android
scope. Read more about publishNonDefault
here.
library/build.gradle
android {
publishNonDefault true
productFlavors {
sdk1521 {
minSdkVersion 15
targetSdkVersion 21
compileSdkVersion 21
}
sdk1524 {
minSdkVersion 15
targetSdkVersion 24
compileSdkVersion 24
}
}
}
For using these two product flavors in the demo app, I created two different configurations with the same SDK versions.
app/build.gradle
android {
// Other configurations and buildtypes
productFlavors {
app1521 {
minSdkVersion 15
targetSdkVersion 21
compileSdkVersion 21
applicationId "xyz.sahildave.flavoredlibrary.app1521"
}
app1524 {
minSdkVersion 15
targetSdkVersion 24
compileSdkVersion 24
applicationId "xyz.sahildave.flavoredlibrary.app1524"
}
}
}
configurations {
app1521DebugCompile
app1524DebugCompile
}dependencies {
compile 'com.android.support:appcompat-v7:24.2.0'
app1521DebugCompile project(path: ':mylibrary', configuration: 'sdk1521Debug')
app1524DebugCompile project(path: ':mylibrary', configuration: 'sdk1524Debug')
}
I have only created the debug configurations because I wanted to test the integration in the demo app and not release it on any store.
Potential Bugs
- Mismatched Support Library Version in the Library Module
While you are building the flavored library, you’d want to use the correct support library with the corresponding library flavor, i.e.
library/build.gradle
android {
publishNonDefault true
productFlavors {
sdk1521 {
minSdkVersion 15
targetSdkVersion 21
compileSdkVersion 21
}
sdk1524 {
minSdkVersion 15
targetSdkVersion 24
compileSdkVersion 24
}
}
}dependencies {
// compile 'com.android.support:appcompat-v7:24.2.1'
sdk1524Compile 'com.android.support:appcompat-v7:24.2.1'
sdk1521Compile 'com.android.support:appcompat-v7:21.0.3'
}
Lint does not give a warning for flavored apps (tried in AS 2.1 and AS 2.2) but you can try to replicate it by creating a separate application module and setting compileSdkVersion
and support library version different. You’d see this lint warning:
This support library should not use a different version (24) than the compileSdkVersion (21):
There are some combinations of libraries, or tools and libraries, that are incompatible, or can lead to bugs. One such incompatibility is compiling with a version of the Android support libraries that is not the latest version (or in particular, a version lower than your targetSdkVersion.)
2. Multiple Support Library Versions in App Module
Now that the library variants are dependent on different versions of support library, the current dependencies system in the app module would use two versions of support library at the same time for the flavor 1521. To cope with this problem, you should make the app variants depend upon the correct support library versions as well.
app/build.gradle
android {
// Other configurations and buildtypes
productFlavors {
app1521 {
minSdkVersion 15
targetSdkVersion 21
compileSdkVersion 21
applicationId "xyz.sahildave.flavoredlibrary.app1521"
}
app1524 {
minSdkVersion 15
targetSdkVersion 24
compileSdkVersion 24
applicationId "xyz.sahildave.flavoredlibrary.app1524"
}
}
}configurations {
app1521DebugCompile
app1524DebugCompile
}dependencies {
// compile 'com.android.support:appcompat-v7:24.1.0'
app1521DebugCompile project(path: ':mylibrary', configuration: 'sdk1521Debug')
app1524DebugCompile project(path: ':mylibrary', configuration: 'sdk1524Debug')
app1521DebugCompile 'com.android.support:appcompat-v7:21.0.3'
app1524DebugCompile 'com.android.support:appcompat-v7:24.2.0'
}
3. AppCompatActivity not available in flavor targeting 21
Now that we have made the flavor app1521
and sdk1521
dependent on support-library 21+, we can’t extend the good old AppCompatActivity
and have to fallback to ActionBarActivity
. For this we use the flexibility of sourcesSets. We create the parallel folders and abstract out the superclass for LibraryActivity
. This way we can have different superclass dependent upon the flavor of the library we are dealing with. The plan is to make the FlavoredActivity
extend ActionBarActivity
in sdk1521
and AppCompatActivity
in sdk1524
:
library
|
|--src
| |--main
| | |--java
| | | |--xyz.sahildave.mylibrary
| | | | |--LibraryActivity.java
| |
| |--sdk1521
| | |--java
| | | |--xyz.sahildave.mylibrary
| | | | |--FlavoredActivity.java
| |
| |--sdk1524
| | |--java
| | | |--xyz.sahildave.mylibrary
| | | | |--FlavoredActivity.java
Flavor: sdk1521
public abstract class FlavoredActivity extends ActionBarActivity {
abstract String generateLibraryText();
}
Flavor: sdk1524
public abstract class FlavoredActivity extends AppCompatActivity {
abstract String generateLibraryText();
}
Now that we have abstracted out the FlavoredActivity
, we can extend it in our main LibraryActivity
. The same thing can be done for other flavor dependent entities.
You can see all of this running in action at this project repo. https://github.com/sahildave/Flavored-Library