14

I am trying to rename my APK files for each build variant to include the application name, versionName, versionCode and build number when present. So far I have everything working except the application name.

I want to use the same value that the AndroidManifest.xml file uses for android:label. This comes from a string resource @string/app_name. I have seen the ability to replace the resource values by using:

resValue "string", "app_name", "Some new value"

But I would just like to read this value and use it to name my APK file.

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        renameApk(variant, output)
    }
}

def renameApk(variant, output) {
    def apkPath = output.outputFile.parent
    def baseName = project.archivesBaseName

    baseName += "-${variant.buildType.name}"

    // add version name and version code
    baseName += "-v${variant.mergedFlavor.versionName}-${variant.mergedFlavor.versionCode}"

    // if built on jenkins ci, add jenkins build number:
    def buildNumber = System.getenv('BUILD_NUMBER')
    if (buildNumber && buildNumber.size() > 0) {
        baseName += "-b${buildNumber}"
    }

    // if the variant will not be zipAligned, specify that
    if (!output.zipAlign) {
        baseName += '-unaligned'
    }

    // set the output file
    output.outputFile = new File(apkPath, "${baseName}.apk");
}
wake-0
  • 3,918
  • 5
  • 28
  • 45
steji113
  • 335
  • 4
  • 13

3 Answers3

11

I don't see any method in Android Plugin docs for accessing resources, so here is the code you can use to find your app's name by searching resources:

def getAppName() {
    def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
    return new XmlParser().parse(stringsFile).string.find { it.@name.equals 'app_name' }.text()
}

BUT I completely agree with @Samuil Yanovski in that it is not worth it - better hardcode a string. I don't think it will slow down building process, but it is just unnecessary.

Yaroslav
  • 4,750
  • 3
  • 22
  • 33
  • 1
    not working with build tools 3.2, some error at `def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }` line. – Yuri Misyac Sep 25 '18 at 09:18
1

I don't think this can be done easily. Resource resolution is done on the mobile device to accommodate for things like screen orientation, localization and so on. The Gradle build system has no way of knowing which locale to use for example. If you insist on getting the value from the resources, you can open the specific strings.xml file you'd like to use, parse the XML and get the value yourself. In my opinion this is a huge overkill and would be pretty slow and ugly.

App name is not changed often, so I would be comfortable with having it hardcoded (especially since the apk file name is not visible to the end user, so even if mistakes happen, the impact would be minimal). If you are working on a white label application and have to support dynamic app name, extracting the value to the gradle.properties file (or some other type of configuration file, you are using) should be a better option rather than using the app's resources.

Samuil Yanovski
  • 2,837
  • 17
  • 19
  • It is indeed for a white label application. I was intending on just ensuring that our build outputs were named differently based on the current vendor we are building for. That way our QA department can easily tell the differences based on APK name. Would you have an example of using the gradle.properties? – steji113 May 27 '16 at 16:46
  • @steji113, could you tell me how are you distinguishing different labels - are you using separate git branches, product flavors or something else? I'll try to figure out a nice way to generate the app name based on this. – Samuil Yanovski May 27 '16 at 19:34
  • So we had a white labeling infrastructure in place before product flavors were introduced. We basically wrote a python script that would replace some string resources to the OEM values and then run a build. Currently our Jenkins build environment pulls the latest code, runs the python OEM script, then performs a gradle build and lastly reverts the changes made by the OEM script. We are willing to do this a different way now that gradle may offer an easier approach. We just aren't quite sure what that is. – steji113 Jun 01 '16 at 20:15
  • @steji113, I would create a separate product flavour for each OEM and assign a custom source set to each flavour. The important part is to have a separate resource folder for the brands (no need to override the other source directories). After this, you can use the default res folder to store all shared resources and put the OEM specific values in the custom directories. Gradle will combine the resources when making a build so you won't have to do anything else. – Samuil Yanovski Jun 06 '16 at 10:42
  • Awesome, this seems to be the way to go to get everything working properly. I will give this a try. – steji113 Jun 07 '16 at 03:17
1

I have create method using @Yaroslav's answer (https://stackoverflow.com/a/37432654/6711554).

def getApplicationName() {
  try {
     def stringsFile = file("./src/main/res/values/string.xml")
     return new XmlParser().parse(stringsFile).string.find { it.@name.equals 'your_app_name' }.text()
  }catch(e){
     println(e)
     return "Default App Name"
  }
}

You can read any string in your gradle from your any resource file.