I'm working on an Android application with Gradle as its build system.
My objective is to use a value (a package name) as an applicationId:
productFlavors {
orange {
applicationId "com.fruits.android.orange"
// ...
But also to expose it via BuildConfig so that Java code has access to it.
This access has to be from outside the flavor (namely, free version of the app needs to know the package name of the paid version so that it can prompt user for an upgrade in Play store).
So I'd like to do something like that:
productFlavors {
orange {
applicationId orangeProPackage
// ...
buildConfigField 'String', 'ORANGE_PRO_PACKAGE', "$orangeProPackage" // ?
Only I'm not sure how to define orangeProPackage so that it's visible in the entire build.gradle and doesn't break the script.
Since there's a few different flavors, it would be best if I could somehow group all these constants like that (I guess?):
def proPackages = [
orange: "..."
apple: "..."
banana: "..."
]
and then refer to them in a clean and descriptive manner like proPackages.orange etc.
The question is, how to accomplish that?
This is not a duplicate of Is it possible to declare a variable in Gradle usable in Java?
I've seen that question (and a few others). I know how to declare buildConfigFields, I already have plenty. My question is about reusing the same value as a buildConfigField and applicationId.
Only I'm not sure how to define orangeProPackage so that it's visible in the entire build.gradle and doesn't break the script.
You could put it in gradle.properties in your project root. Like other .properties files, it's just a key-value store:
ORANGE_PRO_PACKAGE=com.morawski.awesomeapp
You then refer to it as a simple global string variable (ORANGE_PRO_PACKAGE) in your build.gradle:
buildConfigField 'String', 'ORANGE_PRO_PACKAGE', '"' + ORANGE_PRO_PACKAGE + '"'
it would be best if I could somehow group all these constants
Anything involving .properties files won't handle that. There, you may be looking at defining globals in the top-level build.gradle file just in plain Groovy code or something.
you could use extentions like this:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.6.10'
ext.app_version_code = 1010
ext.app_version_name = '1.0.1.0'
}
Related
I'm currently working on modularizing my application, so I'm putting each feature in a library module. In one of my features, I access the version code and name. However, although I defined them in the build.gradle file specific for that module
they were not there in the generated BuildConfig file
I cannot reference the BuildConfig file of the original :app module since I cannot let my feature modules have a dependency on that module.
is there an alternative way of accessing this info from the library module?
Version code and version name are properties of an application package and they are not supported for a library module.
At runtime, you can pass in a Context and access this application package metadata with the help of PackageManager. Example: Detect my app's own android:versionCode at run time
While the answer above is okay, IMO it's not the best way to go about it. Generally speaking, you want your version name and code stored somewhere outside of the build.gradle file. Very often we do that so we can easily access it from the outside of the app, or to update it automatically for CI systems, etc.
A very simple example: You can put those in the gradle.properties file, like so:
<!-- add to the end of the file: -->
VERSION_NAME=0.0.1
VERSION_CODE=1
Then you can simply access them in any build.gradle via properties object, and add them to BuildConfig like so (note: they will only become available after a successful build):
// build.gradle file of the chosen module
def versionName = properties["VERSION_NAME"]
def versionCode = properties["VERSION_CODE"]
buildTypes.all {
buildConfigField "String", "VERSION_NAME", "\"$versionName\"" // Escaping quote marks to convert property to String
buildConfigField "int", "VERSION_CODE", "$versionCode"
}
Alternatively, you can put those in a dedicated version.properties file. Then you can do the same like this:
def versionProps = loadPropertiesFile(project.file('version.properties'))
buildTypes.all {
buildConfigField "String", "VERSION_NAME", getPropEscaped("VERSION_NAME", versionProps)
buildConfigField "int", "VERSION_CODE", getProp("VERSION_CODE", versionProps)
}
Let's say we have strings_test.xml, which stores string values for testing and should be shown in a debug-release. When the apk gets build as a release version all values should be change to an empty string e.g. <string name="test_some_card_text">#string/empty</string>.
Is there a possibility to achieve this?
As always, thanks in advance.
Yes you can do that inside your app gradle under buildTypes..
buildTypes {
mybuild {
resValue "string", "test_some_card_text", '"test"'
resValue "string", "other_text", '"other"'
}
debug {
resValue "string", "test_some_card_text", '"test"'
resValue "string", "other_text", '"other"'
}
}
Then access it like this.
getApplicationContext().getResources().getString(R.string.test_some_card_text);
getApplicationContext().getResources().getString(R.string.other_text);
For build you need to select that build variants and have to build it.
Yes, Gradle lets you override strings.
Add this inside buildTypes{} in your app/build.gradle
debug {
applicationIdSuffix "debug"
}
That should create a directory titled debug next to main. If not then manually create one. (Seriously, I haven't tried this, but I know this is possible.)
Then if your strings_test.xml is under res/values, create similar directory structure under debug/ and put your strings_text.xml with debug specific strings there. This will show up in your debug build. The ones under release/main/res/values will show up in your release build.
PS: You can override all res and asset data like this according to buildTypes and flavor. You can't override Java files though, you could however add them.
As #Aditya Naik said it is possible using Flavors.
Official doc says
BuildType -> Flavor -> main -> Dependencies.
This means that if a resource is declared in both the Build Type and in main, the one from Build Type will be selected.
Note that for the scope of the merging, resources of the same (type, name) but different qualifiers are handled separately.
This means that if src/main/res has
res/layout/foo.xml
res/layout-land/foo.xml
and src/debug/res has
res/layout/foo.xml
Then the merged resource folder will contain the default foo.xml from src/debug/res but the landscape version from src/main/res
for more info visit Official doc - Resource Merging
It is not possible to change the string value after creation of the apk.
But you can assing the value to text or edittext ... etx dynamically after creation of the apk.
For those who come here looking for some way to apply a similar method to raw resources, I dealt with it using buildConfigField.
gradle
...
buildTypes {
debug {
...
buildConfigField "int", "shared_resource_name", 'R.raw.debug_resource_name'
...
}
prod {
...
buildConfigField "int", "shared_resource_name", 'R.raw.prod_resource_name'
...
}
}
Pay attention to the quotes. After that, place BuildConfig.shared_resource_name in the files wherever R.raw.resource_value used to be accessed directly.
This can be used to other resources I think.
I want to automate work with flavors in Android project, using custom Gradle tasks.
For example, implement addFlavor task with 2 parameters: flavorName and flavorLogo. The task should add new flavor, by making next steps:
Update productFlavors section in build.gradle file, by adding a new flavor to it:
flavorName {
buildConfigField 'String', 'PARTNER', '"flavorName"'
packageName 'com.stackoverflow.askquestion.flavorname'
}
Add flavor folder and copy flavorLogo .png file to flavorName/res/drawable-xxhdpi/ic_launcher.png
What is the correct and canonical way to do it with custom a Gradle task?
You cannot use tasks to create Flavors. Flavors describe the model used to create tasks, so it won't work the other way around.
However, you can diretly put code in your build.gradle that will create this for you everytime the build.gradle is executed (basically everytime you run a Gradle command). You could write something like:
String[] customFlavors = ['flavor1', 'flavor2']
customFlavors.each { name ->
def flavor = android.productFlavors.create(name)
flavor.buildConfigField('String', 'PARTNER', "\"${name}\""
flavor.packageName("com.so.question.${name}")
}
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).
I want to override some strings in my res/strings.xml with gradle.
I know that since Android Gradle Plugin 0.7.+ theres the possibilty to have a variant specific source folder.
But my app has a lot of flavors and I don't want to add additionally variant specific folders.
UPDATE 2014-01-17
What I want in detail:
I have some variables in my Resources that are depending only by the buildType (e.g. "release").
First I thought my SOLUTION_1 (override data after resources were merged) is nice, because if I have to change these variables I just have to change them in the build.config (just one place).
But as Scott Barta wrote in the comment below there are some good reasons why this solution is NOT a good idea.
So i tried another solution SOLUTION_2 (just merge the right resources) based on this GitHub project of shakalaca. I think this way is more elegant and I still have the advantage just to change the variables in one place!
SOLUTION_1 (override data after resources were merged):
What I did in AS 0.4.2:
in build.gradle I try to override the string "Hello World" to "OVERRIDE" (based on my answer at this post):
android.applicationVariants.all{ variant ->
// override data in resource after merge task
variant.processResources.doLast {
overrideDataInResources(variant)
}
}
def overrideDataInResources(buildVariant){
copy {
// *** SET COPY PATHS ***
try {
from("${buildDir}/res/all/${buildVariant.dirName}") {
// println "... FROM: ${buildDir}/res/all/${buildVariant.dirName}"
include "values/values.xml"
}
} catch (e) {
println "... EXCEPTION: " + e
}
into("${buildDir}/res/all/${buildVariant.dirName}/values")
// println "... INTO: ${buildDir}/res/all/${buildVariant.dirName}/values"
// --- override string "hello_world"
filter {
String line ->
line.replaceAll("<string name=\"hello_world\">Hello world!</string>",
"<string name=\"hello_world\">OVERRIDE</string>");
}
// *** SET PATH TO NEW RES ***
buildVariant.processResources.resDir = file("${buildDir}/res/all/${buildVariant.dirName}/values/values/values.xml")
// println "... NEW RES PATH: " + "${buildDir}/res/all/${buildVariant.dirName}/values/values/values.xml"
}
}
The copy and filter task works fine, but I couldn't set the "new" values.xml as string resource.
SOLUTION_2 (just merge the right resources)
define a floavor for specific buildType (e.g. "releaseRes")
merge this resourses with the flavor you want to build:
android.applicationVariants.all{ variant ->
variant.mergeResources.doFirst{
checkResourceFolder(variant)
}
}
def checkResourceFolder(variant){
def name = variant.name;
if(name.contains("Release")){
android.sourceSets.release.res.srcDirs = ['src/releaseRes/res']
android.sourceSets.flavor1.res.srcDirs = ['src/flavor1/res']
}
}
You should strive to come up with a solution that doesn't involve writing any custom code in your build files, especially code that does tricky things with reassigning source sets on the fly. Custom Gradle code is a little funky to write, and it's difficult to debug and maintain. The new build system is extremely powerful and already has tons of flexibility, and it's likely that you can already do what you want; it's just a matter of learning how.
Especially if you're just learning the ins and outs of Android-Gradle projects (and it's so new that we all are), it's best to try hard to work with the functionality built into the system before thinking outside the box.
Some recommendations:
It's unlikely you need to vary resources based on build type. A build type in Android-Gradle is supposed to be something like debug or release, where the difference is in debuggability, compiler optimization, or signing; build types are supposed to be functionally equivalent to each other. If you look at the properties you can set on a build type through the Groovy DSL, you can see the intent: debuggable, jniDebugBuild, renderscriptDebugBuild, renderscriptOptimLevel, packageNameSuffix, versionNameSuffix, signingConfig, zipAlign, runProguard, proguardFile, proguardFiles.
If you still think you want to vary resources based on build type, there's already an easy way to do that with the current build system. You can have a build-type-specific resource directory, put your resources in there, and the resource merging in the build system will take care of things for you at build time. This is one of the powerful features in Android/Gradle. See Using Build Flavors - Structuring source folders and build.gradle correctly for information on how to make that work.
If you want to vary something based on build type and your needs are very quick and simple, you might want to do the switch in Java code instead of resources and instead of in the build system. There's the BuildConfig mechanism for that sort of thing -- it's a Java class that defines a DEBUG flag based on debug/release build status, and you can add your own custom Java code from different build types to do more meaningful things. BuildConfig was intended for allowing small functional differences between build types, for cases where a debug build might want to perform some wasteful operation to assist in development, like doing more extensive data validation or creating more detailed debug logging, and those wasteful things are best optimized out of release builds. Having said that, it might be an appropriate mechanism to do what you want.
Consider using flavors for what you're using build types for now. Conceptually a flavor is kind of like a build type in that it's another variant of your application that can be built; the build system will create a matrix of flavors vs. build types and can build all combinations. However, flavors address a different use case, where different flavors share most code but can have significant functional differences. A common example is a free vs. paid version of your application. Inasmuch as a different resource in different variants of your app represents different functionality, that might indicate a need for a different flavor. Flavors can have different resource directories that are merged at build time in the same way as build configs; see the question linked above for more info.
I don't believe you need to customize the build script at all to achieve what you want. According to my reading of http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants; when the build runs, resources will be merged from the following folders, if they exist;
src/[flavour][buildType]/res
src/[buildType]/res
src/[flavour]/res
src/main/res
So I believe you can achieve what you want by simply add the resources in src/release/res.
Though you can tweak the folder names by specifying the relevant sourceSets.[type].res.srcDirs if you really want to change them.
If anyone stumble upon this
buildTypes {
debug{
buildConfigField "String", "Your_string_key", '"yourkeyvalue"'
buildConfigField "String", "SOCKET_URL", '"some text"'
buildConfigField "Boolean", "LOG", 'true'
}
release {
buildConfigField "String", "Your_string_key", '"release text"'
buildConfigField "String", "SOCKET_URL", '"release text"'
buildConfigField "Boolean", "LOG", 'false'
}
}
And to access those values using build variants:
if(!BuildConfig.LOG)
// do something with the boolean value
Or
view.setText(BuildConfig.yourkeyvalue);