Squeezing your Gradle builds

Android 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