«

jul 21 2015

Exprimiendo las ‘builds’ con Gradle

Android Studio llegó de la mano de Gradle como nueva herramienta para la construcción y empaquetado de proyectos Android.

Esta potente utilidad, muchas veces ignorada, puede aportar mucho poder y comodidad a la hora de desarrollar un proyecto complejo: diferentes módulos, variantes, muchas dependencias, sistemas de integración continua, calidad de código, etc.

La motivación de este artículo no es más que compartir sencillas metodologías que he aplicado alguna vez en determinadas circunstancias, por lo que si consigo ayudar lo más mínimo a alguien, habré cumplido mi objetivo!

gradle

Todo el código como siempre se encuentra subido a Github, si alguien encuentra algún error, o tiene alguna recomendación, estaré encantado de discutir!.

https://github.com/saulmm/Gradle-Stuff

Trabajando con diferentes ‘flavors’

Un ‘flavor’, define una versión customizada de una aplicación, imaginemos que tenemos tres flavors’ básicos, un flavor’ para la ‘build’ de pago, otro para la gratuita, y uno último para promociones

/ presentation / build.gradle

  productFlavors {

      paid {}

      free {}

      promo {}
  }

Para configurarlos, podríamos poner sus diferentes atributos dentro de cada uno, en vez de eso, tendremos un archivo aparte llamado variants.gradle, con las diferentes propiedades de cada variante, de tal forma, las propiedades se verán más claras e independizadas en un solo lugar.

/ 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
    ]

Para poder utilizar los atributos de este archivo, es necesario ser aplicado en el build.gradle de la raíz del proyecto.

/ build.gradle

apply from: 'variants.gradle'

Los atributos  que están dentro de ext { } en el archivo variants.gradle, pueden ser accedidos del siguiente modo:

rootProject.ext.{field}

Nuestro archivo build.gradle quedaría de esta forma:

{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
    }
}

Dentro del árbol del directorios del proyecto, Android destina los nombres de los ‘flavors’ para gestionar un directorio donde se almacenen sus recursos o clases específicas.

Screen Shot 2015-07-20 at 03.43.52

Estos recursos y propiedades también se pueden gestionar directamente desde Gradle, opción por la que me he decantado ya que me parece una manera más clara de mantener las propiedades específicas de cada ‘flavor’.

Ahora imaginemos que tenemos algún que otro ‘flavor’ más:

 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
    }
  }

El archivo build.gradle del módulo android se vuelve largo y repetitivo. Con el objetivo de automatizar la tarea de asignación de recursos y propiedades de cada ‘flavor’,  consultamos la información del DSL del plugin de Android, podemos ver que productFlavors delega de la clase: NamedDomainObjectContainer, ésta, aporta un método whenObjectAdded que permite realizar una acción cuando un objeto, en este caso nuestro ProductFlavor.

productFlavors.whenObjectAdded { flavor ->

    def flavorData = rootProject.ext[flavor.name]
    flavor.applicationId paid.packageName

    flavor.buildConfigField 
        'boolean', fieldShowAds, flavorData.showAds
    flavor.resValue 
        'string', resAppName, flavorData.appName
    flavor.resValue 
        'color', resAppColorName, flavorData.appColor
}

Así, podemos automatizar la declaración de los flavors’ , nuestro archivo quedaría de la siguiente manera:

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 {}
    }
}

Trabajando con módulos

Trabajar con módulos independientes, clarifica el proyecto, sobretodo si se usa una arquitectura por capas, en la que cada módulo representa una capa de la arquitectura.

Los módulos son declarados en el archivo settings.gradle, indicando así cúales forman parte del proyecto.

include ':presentation', ':domain', ':model'

Para usar las clases de un módulo dentro de otro, como dependencia es necesario compilar el módulo necesario:

dependencies {
     compile project(':domain')
}

Dependencias

Fernando Cejas, ofrece en su blog buenos ejemplos de cómo gestionar las dependencias de diferentes módulos, en este ejemplo tenemos un archivo en la raíz del proyecto llamado dependencies.gradle en el que estipulamos las dependencias a usar en cada uno de los módulos.

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",]
}

En el build.gradle raíz aplicamos ese archivo:

apply plugin: 'com.android.application'

De tal forma ya la podemos usar en cada uno de nuestros módulos:

android {

  def presentationDependencies  = rootProject.ext.presentationDependencies
  compile   presentationDependencies.butterKnife
  compile   presentationDependencies.recyclerView
  compile   presentationDependencies.appCompat
  compile   presentationDependencies.supportAnnotation
  compile   presentationDependencies.rxAndroid
  compile   presentationDependencies.rxJava
}

Referencias:

Acerca del autor

Saúl Molinero Malvido

Android enthusiast. GDG Vigo & GDG Santiago, en twitter @_saulmm