19

I made a new app with gradle in Android Studio, and now I need to make about 10 versions with different package names and values in resources. I made custom flavors as in example and want to replace some strings in this custom flavors with custom values. I found example like this:

filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: ['version': '2.2'])

But i don't know where to put it. As i understand i need to put it into separate task, but how to make this task called by IDE?

Also i need to replace few variables inside Java classes and Content Provider's auth, maybe i need to do this by copy files into flavor1 folder and let gradle to merge it, but it seems like wrong solution to store many copies of files with difference in one line... Maybe i need to user some other solution for all this?

Here is build.gradle:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.4.2'
    }
}
apply plugin: 'android'

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    compile project(':JazzyListView')
    compile project(':ABS')
    compile project(':Volley')
}

android {
    compileSdkVersion 17
    buildToolsVersion "17.0.0"

    defaultConfig {
        versionCode 5
        versionName "3.0"
        minSdkVersion 8
        targetSdkVersion 17
    }

    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java']
            res.srcDirs = ['src/main/res']
        }
    }

    productFlavors {
        flavor1 {
            packageName "com.example.flavor1"               
        }

        flavor2 {
            packageName "com.example.flavor2"
        }

    }

}
itspers
  • 797
  • 1
  • 4
  • 13

4 Answers4

18

I had a similar problem. I wanted to add the Jenkins build number to the strings that get merged from strings.xml. Here's my solution as of Android Gradle plugin 0.12.+.

// Insert the build number into strings.xml
android.applicationVariants.all{ variant ->
    variant.mergeResources.doLast{
        ext.env = System.getenv()
        def buildNumber = env.BUILD_NUMBER
        if (buildNumber != null) {
            File valuesFile = file("${buildDir}/intermediates/res/${variant.dirName}/values/values.xml")
            println("Replacing revision number in " + valuesFile)
            println("Build number = " + buildNumber)
            String content = valuesFile.getText('UTF-8')
            content = content.replaceAll(/devBuild/, buildNumber)
            valuesFile.write(content, 'UTF-8')
        }
    }
}

You might want to hook into a different Gradle task depending on what you want to do. Take a look at the tasks that are part of the Android build to figure that out.

http://tools.android.com/tech-docs/new-build-system/user-guide

UPDATE: At some point, the Android Gradle plugin changed the way to iterate through application variants keyword from each to all. My answer has been updated to reflect the change, but try switching to each if this code doesn't print anything to the console.

goldierox
  • 1,085
  • 1
  • 8
  • 23
  • 1
    I have a doubt I have researched the folder structure, in `${buildDir}` there is a folder `rs` in `source` folder ` and a `res` folder, I doubt they are similar, but why the `source/rs` is empty , though ("${buildDir}/res/all/${variant.dirName}/values/values.xml")` can solve the problem but how about modify a value-14.xml? – aelam Sep 04 '13 at 07:55
  • @aelam, I believe the `rs` directory is for RenderScript, while the `res` folder is for your resources. If you wanted to modify a "values-[API-level].xml" file, you could probably use a regex or iterate through the files in that directory. I don't know how to off the top of my head. I'd recommend checking out the Gradle documentation. http://www.gradle.org/docs/current/userguide/working_with_files.html – goldierox Sep 04 '13 at 17:25
  • 1
    I managed to get this working in 0.8 with: File valuesFile = new File("${buildDir}/intermediates/res/${variant.dirName}/values/values.xml") but it requires a gradlew clean first, looking for a better solution. – eski Jul 07 '14 at 15:56
  • @eski I think you're referring to the Android Studio version number, but the build output directory structure is tied to the Android Gradle plugin. Both are paired tightly though. I added your update to my answer. – goldierox Jul 09 '14 at 15:23
  • Hacky solution, but it works. This will hopefully be easier soon once this issue gets resolved: https://code.google.com/p/android/issues/detail?id=67416 – jenzz Sep 22 '14 at 11:24
  • 1
    FYI, this solution no longer works with Android Gradle Plugin 3.0.0 / AAPT2. The reason is AAPT2 changes how resources are processed and merged. The current workaround is to disable AAPT2 or to modify the resources ahead of the build. I have verified that disabling AAPT2 works, although is not ideal. More details: https://twitter.com/ashughes88/status/929092362248130561 – ashughes Nov 15 '17 at 20:30
4

I was trying to get similar functionality as Maven resource filtering. This is what I came up with. My solution could use some changes to be more robust (i.e. pulling from a properties file, etc).

My example just shows how to replace a single value, which is all that I needed. The variables follow the ${some.property} convention. This solution also works with product flavors that have their own resource files.

import org.apache.tools.ant.filters.*
...
android.applicationVariants.all{ variant ->
    // Perform resource filtering
    variant.mergeResources.doLast {
        filterResources(variant)
    }
}

def filterResources(buildVariant) {

    //Setup temp directory to filter the resources
    File resFiltered = file("${buildDir}/res/all/filtered/${buildVariant.dirName}")
    if(resFiltered.exists()){
        resFiltered.delete()
    }

    //Copy and filter the resources.
    copy {
        from(buildVariant.processResources.resDir) {
            include '**/*.xml'

            //Could be improved upon to pull from a properties file, etc.
            ant.properties['app.version'] = project.version

            filter(ExpandProperties, project: ant.project)

        }
        from(buildVariant.processResources.resDir) {
            exclude '**/*.xml'
        }

        into resFiltered

    }

    //Delete all the original resource files
    file(buildVariant.processResources.resDir).deleteDir()
    //Replace with the filtered ones.
    resFiltered.renameTo(file(buildVariant.processResources.resDir))
    //Delete the original 'filtered' directory
    file( "${buildDir}/res/all/filtered").deleteDir()
}

Example in strings.xml

 ...
 <string name="app_version">${app.version}</string>
 ...
Tom Bollwitt
  • 10,849
  • 1
  • 17
  • 11
  • Hi i have some problem with implementing this, i get the exception: Execution failed for task ':app:mergeDebugResources'. > No such property: ExpandProperties for class: org.gradle.api.internal.file.copy.CopySpecWrapper_Decorated. Do you know what to do with it? – Billda Jun 30 '14 at 09:43
  • what version of gradle are you using? – Tom Bollwitt Jul 03 '14 at 19:13
  • This doesn't work with AAPT2 because the .xml files are flattened before being written to the disk. See: https://issuetracker.google.com/issues/65220623 – Mark Oct 16 '17 at 12:56
3

These links may be helpful:

And my filter definition using regex to replace something:

from('res/') {
    include '**/*.xml'
    filter {
        line -> line.replaceAll(/YOUR_REGEX_TO_REPLACE_SOMETHING/, 'REPLACED_RESULT_EXPRESSION')
    }
}
into 'build/path/to/your/filtered/resources/'
Intae Kim
  • 309
  • 1
  • 5
  • 2
    Thanks, but how to define this task and how to use it in build type or flavor? – itspers Jun 26 '13 at 12:09
  • @itspers See my answer for more info on how to modify a specific build variant – goldierox Jul 24 '13 at 16:57
  • the problem is where is the `'build/path/to/your/filtered/resources/' path ` – aelam Sep 04 '13 at 07:32
  • when you run gradle script, 'build' is sub directory produced on your project root; `gradle clean` deletes 'build'. – Intae Kim Sep 05 '13 at 07:10
  • @aelam location for such build artifacts is your own choice, but it would be convenient to follow gradle convention. with above relative path notation start with `build`, you can find filtered resources on your `$PROJECT_ROOT/build/path/to/filtered/resources` after ran gradle script. – Intae Kim Sep 05 '13 at 07:31
  • @IntaeKim `File valuesFile = file("${buildDir}/res/all/${variant.dirName}/values/values.xml")` I replace text in this file and it works. do you mean it? – aelam Sep 06 '13 at 10:24
  • I have got another two problems, I have 40 productFlavors, it causes "Java heap space" and I have 4 buildTypes, the debug Type can run normally, but the release Type crashes,because a Class in a lib cast to another class? do you have this problem ? – aelam Sep 06 '13 at 11:03
2

In your "src" folder, create "flavor1" and "flavor2" folders with the same hierarchy as your "main" folder. If you have strings.xml in your main/res/values, you can do it in flavor1/res/values as well and have the replacement values in there. It may show errors in the IDE but it should still build and run.

snotyak
  • 3,709
  • 6
  • 35
  • 52