Squeezing your Gradle builds
20 Jul 2015Android Studio came from by the hand of Gradle as a new tool for the construction and packaging of Android projects. This powerful utility, well exploited, can provide much power and comfort developing complex Android projects. These projects may contain different modules, variants, dependencies, continuous integration systems, code quality, etc.
The motivation of this article is nothing more than sharing simple methodologies that I have applied on certain projects.
All the code is uploaded to Github, if someone find any errors, or have any recommendations, will be happy to discuss!.
https://github.com/saulmm/Gradle-Stuff
Working with flavors
Let’s imagine that we have three flavors on our project, a free flavor, paid, and a promo flavor.
productFlavors {
paid {}
free {}
promo {}
}
To configure them, instead of writing directly to the build.gradle
at our android module, we will have a new separate file called variants.gradle
with the different properties of each flavor.
/ variants.gradle
ext {
basePackageName = 'saulmm.gradlestuff'
resAppColorName = 'build_brand_primary'
resAppName = 'build_app_name'
fieldShowAds = 'ADS'
paid = [
packageName : "${basePackageName}.premium",
appName : "Gradle Stuff Premium",
appColor : "#F44336",
showAds : "false",
versionName : "2.3.2",
versionCode : 4
]
free = [
packageName : "${basePackageName}.free",
appName : "Gradle Stuff",
appColor : "#4CAF50",
showAds : "true",
versionName : "4.1.1",
versionCode : 15
]
promo = [
packageName : "${basePackageName}.promo",
appName : "Gradle Stuff Demo",
appColor : "#9C27B0",
showAds : "true",
versionName : "1.0",
versionCode : 1
]
This file is applied in the build.gradle
file from the root of the project. Now the submodules can use these properties.
/ build.gradle
apply from: 'variants.gradle'
Attributes within ext {}
in the file variants.gradle
can be accessed in the following way: rootProject.ext.{field}
/ {android module} / build.gradle
productFlavors {
paid {
def paid = rootProject.ext.paid
applicationId paid.packageName
buildConfigField 'boolean', fieldShowAds, paid.showAds
resValue 'string', resAppName, paid.appName
resValue 'color', resAppColorName, paid.appColor
}
free {
def free = rootProject.ext.free
applicationId free.packageName
buildConfigField 'boolean', fieldShowAds, free.showAds
resValue 'string', resAppName, free.appName
resValue 'color', resAppColorName, free.appColor
}
promo {
def promo = rootProject.ext.promo
applicationId promo.packageName
buildConfigField 'boolean', fieldShowAds, promo.showAds
resValue 'string', resAppName, promo.appName
resValue 'color', resAppColorName, promo.appColor
}
}
For each flavor, Android allocates certain folders for the use of specific resources or flavor classes. We may replace the values variants.gradle
to independent files, for me, is more clear to have everything centralized in one place.
At this point, imagine that we have more flavors:
/ presentation / build.gradle
productFlavors {
paid {
def paid = rootProject.ext.paid
applicationId paid.packageName
buildConfigField 'boolean', fieldShowAds, paid.showAds
resValue 'string', resAppName, paid.appName
resValue 'color', resAppColorName, paid.appColor
}
free {
def free = rootProject.ext.free
applicationId free.packageName
buildConfigField 'boolean', fieldShowAds, free.showAds
resValue 'string', resAppName, free.appName
resValue 'color', resAppColorName, free.appColor
}
promo {
def promo = rootProject.ext.promo
applicationId promo.packageName
buildConfigField 'boolean', fieldShowAds, promo.showAds
resValue 'string', resAppName, promo.appName
resValue 'color', resAppColorName, promo.appColor
}
christmas {
def christmas = rootProject.ext.christmas
applicationId christmas.packageName
buildConfigField 'boolean', fieldShowAds, christmas.showAds
resValue 'string', resAppName, christmas.appName
resValue 'color', resAppColorName, christmas.appColor
}
halloween {
def halloween = rootProject.ext.halloween
applicationId halloween.packageName
buildConfigField 'boolean', fieldShowAds, halloween.showAds
resValue 'string', resAppName, halloween.appName
resValue 'color', resAppColorName, halloween.appColor
}
easter {
def easter = rootProject.ext.easter
applicationId easter.packageName
buildConfigField 'boolean', fieldShowAds, easter.showAds
resValue 'string', resAppName, easter.appName
resValue 'color', resAppColorName, easter.appColor
}
independence {
def independence = independence.ext.christmas
applicationId christmas.packageName
buildConfigField 'boolean', fieldShowAds, independence.showAds
resValue 'string', resAppName, independence.appName
resValue 'color', resAppColorName, independence.appColor
}
}
The build.gradle
file of the android module becomes long and repetitive. If we consult the Android Gradle plugin DSL, we can see that productFlavors
delegates on de class: NamedDomainObjectContainer, this provides a whenObjectAdded method that allows you to perform an action when an object is added, in this case our ‘ProductFlavor’.
/ presentation / build.gradle
productFlavors.whenObjectAdded { flavor ->
def flavorData = rootProject.ext[flavor.name]
flavor.applicationId flavorData.packageName
flavor.buildConfigField
'boolean', fieldShowAds, flavorData.showAds
flavor.resValue
'string', resAppName, flavorData.appName
flavor.resValue
'color', resAppColorName, flavorData.appColor
}
In this way, we can refactor the flavors
/ presentation / build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion androidSdkVersion
buildToolsVersion androidToolsVersion
defaultConfig {
applicationId basePackageName
minSdkVersion androidMinSdkVersion
targetSdkVersion androidSdkVersion
}
buildTypes {
debug {
applicationIdSuffix '.debug'
}
}
productFlavors.whenObjectAdded { flavor ->
def flavorData = rootProject.ext[flavor.name]
flavor.applicationId flavorData.packageName
flavor.buildConfigField
'boolean', fieldShowAds, flavorData.showAds
flavor.resValue
'string', resAppName, flavorData.appName
flavor.resValue
'color', resAppColorName, flavorData.appColor
}
productFlavors {
paid {}
free {}
promo {}
christmas {}
halloween {}
easter {}
independence {}
}
}
Working with modules
Working with independent modules, clarifies the project, especially on a specific architecture where their components are divided into layers.
The modules are declared in the file settings.gradle
, indicating which will be part of the project
include ':presentation', ':domain', ':model'
We need indicate which module we are going to use as dependency, example, if we are at the presentation module and we need classes from domain, we need to compile the domain project:
dependencies {
compile project (':domain')
}
Dependencies
Fernando Cejas, gives a brilliant approach regarding how to deal with dependencies, in this example we have a file in the root of the project called dependencies.gradle
in which stipulate the dependencies to use in each one of the modules.
By the way, the .gradle
files can be ordered in any folder, always indicating the required path.
/ dependencies.gradle
ext {
butterKnifeVersion = '7.0.1'
recyclerViewVersion = '21.0.3'
supportLibrary = '22.2.0'
firebase = '2.3.1'
rxAndroidVersion = '0.25.0'
rxJavaVersion = '1.0.10'
presentationDependencies = [
butterKnife :
"com.jakewharton:butterknife:$butterKnifeVersion",
recyclerView :
"com.android.support:recyclerview-v7:$supportLibrary",
cardView :
"com.android.support:cardview-v7:$supportLibrary",
appCompat :
"com.android.support:appcompat-v7:$supportLibrary",
supportAnnotation:
"com.android.support:support-annotations:$supportLibrary",
rxAndroid :
"io.reactivex:rxandroid:$rxAndroidVersion",
rxJava :
"io.reactivex:rxjava:$rxJavaVersion",]
domainDependencies = [
rxJava: "io.reactivex:rxjava:${rxJavaVersion}",]
modelDependencies = [
fireBase: "com.firebase:firebase-client-android:$firebase",
rxJava : "io.reactivex:rxjava:$rxJavaVersion",]
}
This file is again applied in the build.gradle
file from the root of the project.
/ build.gradle
apply from: 'dependencies.gradle'
/ presentation / build.gradle
apply plugin: 'com.android.application'
android {
dependencies {
def presentationDependencies =
rootProject.ext.presentationDependencies
compile presentationDependencies.butterKnife
compile presentationDependencies.recyclerView
compile presentationDependencies.appCompat
compile presentationDependencies.supportAnnotation
compile presentationDependencies.rxAndroid
compile presentationDependencies.rxJava
}
}
Resources
-
Introduction to Groovy and Gradle - Dan Lew
-
Android 10 coder blog - Fernando Cejas