Sunday, August 3, 2014

Build Variations using Gradle and Resource Folders

    The Android framework allows for customization of apps based on different attributes of a physical device through the structuring of an apps resource folders, letting a developer change things such as layouts, integers and string values in order to fit their apps needs for a given device. Using this, developers can easily change configurations between phones, small and large tablets, and devices in landscape or portrait mode. With the introduction of Gradle and Android Studio, the ability to use folder structures has been expanded to allow for overriding resource folders and values per different product flavors and build types, such as debug and release. In this tutorial I will go over using Gradle to reskin a base application using product flavors. All source code can be found on GitHub.

    To start, let's touch on using resource values in an application. If you append a quantifying value such as -xxhdpi, -land or -sw600dp to a resource folder, then the resource values in those folders will override the values in their main counterpart folder for devices that fit the appended values criteria. This means that if a device is in landscape mode, it will use resources from values-land over those in values if available, and if the device is at least a small tablet, then items in values-sw600dp will take precedence.

    The differences here can be seen in the following example screen shot, where one line of text is changed depending on the default value (for portrait devices) and the values-land string value. Without any code changes, the device will choose which string value to use based on the orientation of the phone.
default orientation string - portrait
landscape orientation string on a Nexus 4

    Now that we have a base understanding of resource hierarchies in the Android framework, let's move on to the overall goal of this tutorial - reskinning an app using product flavors. This is done almost entirely in the build.gradle file, though there will be some additions in the project folder structure. A default Android application in Android Studio will already come with the build type node populated for debug and release.

buildTypes {
    release {
        //This is where the signing cert would be referenced for the release build
    }
    debug {
        //This is where the signing cert would be referenced for the debug build
    }
}

    Under the buildTypes node, we can define the main categories of product flavors that we will want to use. When generating a project, one type of each of these flavors will be used, as well as one of the build types.

flavorDimensions "color", "number"

    Once the types of flavor dimensions are defined, we can start declaring types of product flavors and assigning them to a flavor dimension.

productFlavors {
    blue {
        applicationId "com.ptrprograms.gradleproductflavorsblue"
        flavorDimension "color"
    }
    green {
        applicationId "com.ptrprograms.gradleproductflavorsgreen"
        flavorDimension "color"
    }
    orange {
        applicationId "com.ptrprograms.gradleproductflavorsorange"
        flavorDimension "color"
    }
    purple {
        applicationId "com.ptrprograms.gradleproductflavorspurple"
        flavorDimension "color"
    }
    red {
        applicationId "com.ptrprograms.gradleproductflavorsred"
        flavorDimension "color"
    }
    yellow {
        applicationId "com.ptrprograms.gradleproductflavorsyellow"
        flavorDimension "color"
    }

    one {
        flavorDimension "number"
    }

    two {
        flavorDimension "number"
    }
}

    Note that each of the color flavors has an applicationId value set. This allows us to differentiate between our products, getting multiple reskinned applications onto the Play Store with the same code base. With the product flavors defined, we can now use the Build Variants window in Android Studio to pick a mix of build types and product flavors to install or build.


    Similar to overriding values within one application by device size or orientation, we can also override values by product flavor. By creating directories in the same folder as 'main' titled after their product flavors, the resources in those directories will take precedence over the main resource values. In this example I override all of the launcher icons, the background color resource and a few strings matching the product flavor in each of the color flavors. Likewise I override a string in the number product flavor directories.



    With these values filled out, we can quickly generate similar apps with cosmetic changes, as can be seen from the blue version of the app at the beginning of this post and this green product running on a Nexus 7.

    And with that we are able to generate many different applications using a similar code base with different values per product for colors, images, text and any other available type of resource, so that multiple apps can be built quickly with just some planning and organization.