Android Gradle Read App Name from strings.xml - android

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");
}

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.

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.

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.

Related

Different style applicationId in multiple dimensions

Problem: setting applicationId depending on flavor.
More problem: Two apps are already on the store, both having a different style of applicationId.
com.name.dimension1.dimension2
com.name.dimension1dimension2 (without dot)
In our Android app we need to introduce new flavors/dimensions.
Dimensions:
flavorDimensions "company", "app", "server"
Seeing that, this is why we cannot use applicationIdSuffix in build.gradle because it is automatically adding . (dot) before suffix.
We already have method to decide which versionCode should be done for every flavor (thanks to that answer on Stack)
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find())
return matcher.group(1).toLowerCase()
else {
println "NO MATCH FOUND"
return ""
}
Even more problem: Same method which helps us with setting version code, cannot help with applicationId.
def getFlavorApplicationId() {
def flavor = getCurrentFlavor()
if (flavor.contains("company1") && flavor.contains("app1")) {
return ext.company1app1AppId
} else if (flavor.contains("company2") && flavor.contains("app1")) {
return ext.company2app1AppId
} else if (flavor.contains("company2") && flavor.contains("app2")) {
return ext.company2app2AppId
}
return "nothing"
}
When app is built/synchronized - everything is working properly (file BuildConfig and also generated apk is having correct applicationId).
The problem is occurring when we are trying to Run the app with applicationId is depending on flavor.
Error while executing: am start -n "**non.of.those**/com.rsqtechnologies.rsqphysio.splash.SplashActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=non.of.those/com.rsqtechnologies.rsqphysio.splash.SplashActivity }
Error type 3
Error: Activity class {**non.of.those**/com.rsqtechnologies.rsqphysio.splash.SplashActivity} does not exist.
Error while Launching activity
If I understand this well, Android Studio is not taking applicationId to run the app from BuildConfig or even generated .apk. It is trying to generate it while launching app (when it cannot gather information about flavor from gradle task - def getCurrentFlavor())
when I am running the app by myself in Terminal with the same command but correct appId - everything works fine.
Things which I have already tried also:
Solution from this Stack answer
Tried to find a way in Android Studio to edit the configuration so it would take proper appId (failed)
Does anyone have some advice? Pretty complex problem, I can share more details if anyone interested.
When you are using groovy you can change the applicationId for all your merged flavors:
android.applicationVariants.all { variant ->
def isApp1 = variant.name.contains('app1')
def isApp2 = variant.name.contains('app2')
def idAppendix = ""
if (isApp1) idAppendix = ".withDotForApp1"
if (isApp2) idAppendix = "noDotForApp2"
mergedFlavor.setApplicationId(mergedFlavor.applicationId + idAppendix)
}
Note that this won't work if you plan to use kotlinscript since the applicationid of the merged flavor is a val and doesnt offer a setter.

Generate APK with declared name in Gradle without any suffixes

I know that it is possible to specify APK name by using following command in Gradle build file:
setProperty("archivesBaseName", "something")
There are lots of questions here that deal with this. But doing this also results in APK that has flavor and build variant appended to the name. So using above command results in APK that's named something-dev-debug.apk.
Is there a way to completely specify file name without appending any suffixes. I already have appId in my flavors like this:
//...
productFlavors {
dev {
applicationId "com.myapp.dev"
setProperty("archivesBaseName", applicationId)
}
//...
So ideally I would like to produce APK named com.myapp.dev.apk without any extra suffixes or version numbers. Is this possible?
It seems that this shouldn't be done with setPropery. Rather you can define handler that changes output file name as final step by adding following code to the end of android { } section in gradle file:
applicationVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
def appId = variant.applicationId
def fileName = appId +".apk"
output.outputFile = new File(file.parent, fileName)
}
}

Getting an app_name from strings.xml for current product flavor in gradle

In my app, I have a lot of different product flavors, around 10 actually.
The way they are done is something like : eng_testDebug, eng_prodDebug, eng_testRelease, eng_prodRelease e.t.c for other languages.
So now I'v got a task to add "- Test" to the app name for build with test in the build name.
Right now I found a solution to parse string.xml from main sourceSet and build my app name using manifestPlaceHolders in a gradle file. But it works only for main flavor.
Here is a code for getting default strings.xml app_name I am using:
def getAppName() {
def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
String s = new XmlParser().parse(stringsFile).string.find { it.#name.equals 'app_name' }.text();
return s.replaceAll("\"", "");
}
And here I modify it to add "Test":
def getEditedAppName() {
if (getCurrentFlavor().contains("test")) {
return getAppName() + "(Test)";
} else {
return getAppName();
}
}
getCurrentFlavor() returns me a flavor name used in :assemble task.
Basically, the question is how can I get other sourceSet files depending on my current building flavor so I can parse flavor's app_name?
Unfortunately defying an app_name string for each flavor in a gradle file is not an valid option for me.
For each flavor you can create a folder named like the flavor in you src folder. There you should also see your main folder. In those flavor folders you can create the res folder and override or add additional resource files.
Here you can create the strings.xml and change the app name as well as drawables and so on.
http://ptrprograms.blogspot.de/2014/08/build-variations-using-gradle-and.html
Edit based on comment:
I got these two flavors:
productFlavors {
appFlavorA{
applicationId "de.test.appaA"
}
appFlavorB{
applicationId "de.test.appaB"
}
}
Now I got the following structure for strings.xml
ProjctRoot/app/src/main/res/values/strings.xml
ProjctRoot/app/src/appFlavorA/res/values/strings.xml
ProjctRoot/app/src/appFlavorB/res/values/strings.xml
This will result in the usage of the corresponding strings.xml for the flavorflavor
If I understand the question correctly, you want to have different app names based on your product flavors.
In your gradle file:
android {
productFlavors {
stagingone{
}
stagingtwo{
}
}
}
Assuming that you have this sample entry, just place a strings.xml file that contains different app_name for each of the flavors.
src/stagingone/res/values/strings.xml
src/stagingtwo/res/values/strings.xml
I don't think you can manually do that (change app names at runtime), as highlighted in this Stackoverflow thread.
Android : Change App Label Programatically
But, there is another way. Do it the same, but for app launcher icons for the flavors. Good luck.

Android Studio: Gradle Product Flavors: Define custom properties

I am building different product flavors of an Android App in Gradle (Android Studio).
Hence I defined the following product flavors:
android {
project.ext.set("customer", "")
project.ext.set("server", "")
//Configuration happens here - code removed for readability
buildTypes {
debug {
server = "test"
}
release {
server = "release"
}
}
//Available product flavors
productFlavors {
customerA{
customer = "a"
}
customerB{
customer = "b"
}
customerC{
customer = "c"
}
}
}
However, later on, when I access the defined project property "customer" (whose value is set in the product flavor i am currently building) in one of my build tasks, it always has the value "c" even though iam building customerA (in which case the property customer should be "a" rather than "c"). For instance I execute the following task later on:
preBuild << {
println "Building customer: " + customer
}
and it always prints:
Building customer: c
So i am guessing there is some overwriting happening? Possibly related to the configuration VS execution phase? Not sure how/why though, so any help is be greatly appreciated.
UPDATE: Alternatively it would already get me further to determine the name of the product flavor (without the build type name attached to it) and the build type (again: without the product flavor name prepended to it) during execution phase of the gradle build.
Considering the above configuration the expected product flavor names would be: customerA, customerB and customerC.
During evaluation phase, Gradle executes all of the code in your android block; it doesn't just execute the code relevant to the flavors you want to compile. In fact, during evaluation phase, it doesn't even really know what your flavors are; it has to evaluate that to find out.
So all three of your lines customer = "a", customer = "b", and customer = "c" will get executed.
This is one of the subtle things about Gradle that make it a little difficult to learn.
So I've explained why your code isn't working the way you expect, but this answer is incomplete because I haven't said a lot about what to do to make it work right, but it's hard to say what to do because I'm not sure what you're trying to accomplish. In general I can say that you should think of trying to accomplish what you want using user-defined tasks, and setting up intra-task dependencies to make sure things get executed in the right order. A gotcha with Android Gradle builds is that even those tasks don't get defined until evaluation phase (it can't know what tasks it needs to build all your flavors until it's evaluated the build file and knows what those flavors are), so do some SO sleuthing to see how to hook things onto Android Gradle build tasks -- you have to set up your tasks at the end of evaluation phase after the Android plugin has done its thing.
A lot of thanks goes to Scott Barta, for his suggestions and for explaining, why my solution did not work (which also made me reconsider a few things). I basically came up with different ways to accomplish what I needed.
Unless what you need to do can't be achieved by simply organizing your Android Resource tree based on build types and flavors (i.e. via convention) then I'd recommend option 2. Though I did keep option 1 for reference purposes since it covers the interesting subject of productFlavor property extension.
Custom property-based option: Product Flavors lets you define custom properties and thus extend a productFlavor. An example is provided here by Xavier Ducrohet: https://stackoverflow.com/a/17708357/1041533
I'll offer up a very simple and similar example as provided above, though in my case I needed a String property, rather than a boolean.
// This class will be used to create our custom property
class StringExtension {
String value
StringExtension (String value) {
this.value = value
}
public void setValue(String value) {
this.value = value
}
public String getValue() {
return value
}
}
android {
// Add our new property to each product flavor upon creation
productFlavors.whenObjectAdded { flavor ->
//I am suspecting the last argument is the default value
flavor.extensions.create("myProperty", StringExtension , '')
}
// then we can set the value on the extension of any flavor object
productFlavors {
customerA{
myProperty.value 'customerA'
}
customerB{
myProperty.value 'customerB'
}
}
}
//Adds a custom action to the preBuild task
preBuild << {
//Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
android.applicationVariants.all { variant ->
//Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
variant.productFlavors.each { flavor ->
//Access our custom property "customerName"
println "Building customer" + flavor.customerName.value
}
}
}
I then realized, that the above was totally unnecessary, because all I wanted was the name of my flavor (without the build type in it) and once I found the property that gives me the name of my flavor, I was able to change all of the above code as follows:
Simply use the name of your flavor as the customer's name by accessing the already existent product flavor property called "name".
android {
productFlavors {
customerA{
}
customerB{
}
}
}
//Adds a custom action to the preBuild task
preBuild << {
//Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
android.applicationVariants.all { variant ->
//Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
variant.productFlavors.each { flavor ->
//Access our product flavor name
println "Building customer" + flavor.name
}
}
}
The above makes a lot more sense too, because my directory structure for Android Resources is named after the actual flavors.
The latter also led me to my final solution for the original question:
Resource directory based approach
The intent was to modify a file in the xml folder of each customer based on whether it is a release or a debug build. This can be achieved by a corresponding folder structure. Based on the original question we have 3 customers, and each customer has a debug and a release build. The afore mentioned xml files are different for each customer and build type. Hence the following directory structure:
src/
- customerA
//Contains all relevant resource files specific to customer A
- customerB
//Contains all relevant resource files specific to customer B
- customerC
//Contains all relevant resource files specific to customer C
- customerADebug
//Contains debug server-settings file for customer A
- customerBDebug
//Contains debug server-settings file for customer B
- customerCDebug
//Contains debug server-settings file for customer C
- customerARelease
//Contains release server-settings file for customer A
- customerBRelease
//Contains release server-settings file for customer B
- customerCRelease
//Contains release server-settings file for customer C
So the main content for each product flavor was in the folder with the same name as the flavor (customerA, customerB etc. see first part of above snippet). Now this one file, that different based on whether it was a debug or release build for each customer is put into the appropriate folders such as customerADebug --> contains file with server settings for debug mode etc.
And when you build customerA for instance the correct file will be chosen if you build a debug or release build.
To answer the UPDATE part of my post:
Product flavor name (without buildType):
flavor.name (where flavor is a productFlavor)
The following worked for me to add custom properties to product flavors:
android {
// ...defaultConfig...
productFlavors.whenObjectAdded { flavor ->
// Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
flavor.ext.set('myCustomProperty', 'customPropertyValue')
}
productFlavors {
flavor1 {
}
flavor2 {
myCustomProperty = 'alternateValue'
}
}
}
flavor1 has the default value for the custom property, while flavor2 has the overridden value.
Here's an example how to access the custom property:
applicationVariants.all { variant ->
// Get the 'myCustomProperty' property from the variant's productFlavor (it's a list, but there should only be one)
def customProp = variant.productFlavors*.myCustomProperty[0]
}
I assume the same could be done to add custom properties to build types, but I haven't tested this.

Setting app label based on buildvariant

I have an Android with multiple productFlavors. I'd like the android:label for the debug build of, for example flavor1, to say Flavor1 Debug and the release build to say Flavor1.
Without productFlavors this is relatively simple, just create a different string resource in the debug and release source folders but with productFlavors, I don't want to be creating flavor1Debug, flavor2Debug, etc. folders for each flavor with just one string resource in there.
Is there a way to do this ? I'm guessing it requires merging resources somehow but am not sure how.
For extra points, it would be awesome if I could add an overlay to the app icon i.e. merge two images but I realize that might be taking it too far.
I worked out a solution for this. Basically, you use the following method that copies read the build file, modifies the property value, then rewrites the files.
def debugAppRename(variant, labelResource) {
def flavor = variant.productFlavors.get(0)
def buildtype = variant.buildType
// Append buildType name to app label
if(buildtype.debuggable) {
variant.mergeResources << {
def valuesFile = "$buildDir/res/all/${flavor.name}/${buildtype.name}/values/values.xml"
def values = (new XmlParser()).parse(valuesFile)
values.string.each { m->
if (m.#name == labelResource) {
m.value = m.text() + " " + buildtype.name.capitalize()
new XmlNodePrinter(new PrintWriter(new FileWriter(valuesFile)))
.print(values)
}
}
}
}
}
The way to use it is:
applicationVariants.all { variant ->
debugAppRename(variant, 'app_name') // where `app_name` is the string resource you use for the `app:label` property in your AndroidManifest.xml
}
The resulting app will have the buildType name appended to it if it is a debuggable buildType, for e.g. My App Debug, My App Staging, etc. The release build remains unaffected.
I also put it up on a gist.
You can apply it in your build.gradle using the apply from: directive with the raw gist URL or copy the above to your code (personally I find build scripts become too large so I prefer applying from).

Categories

Resources