Is it possible to declare a variable in Gradle usable in Java?
Asked Answered
G

11

467

Is it possible to declare a variable in Gradle usable in Java ? Basically I would like to declare some vars in the build.gradle and then getting it (obviously) at build time. Just like a pre-processor macros in C/C++...

An example of declaration would be something like that ... :

android {
    debug {
        A_VAR_RETRIEVABLE_IN_JAVA = 42
    }
    release {
        A_VAR_RETRIEVABLE_IN_JAVA = 42+52
    }
}

Is there a way to do something like that ?

Goar answered 19/6, 2013 at 17:32 Comment(0)
C
876

Here are two ways to pass value from Gradle to use in Java;

Generate Java Constants

android {
    buildTypes {
        debug {
            buildConfigField "int", "FOO", "42"
            buildConfigField "String", "FOO_STRING", "\"foo\""
            buildConfigField "boolean", "LOG", "true"
        }

        release {
            buildConfigField "int", "FOO", "52"
            buildConfigField "String", "FOO_STRING", "\"bar\""
            buildConfigField "boolean", "LOG", "false"
        }
    }
}

You can access them with BuildConfig.FOO

Generate Android resources

android {
    buildTypes {
        debug{
            resValue "string", "app_name", "My App Name Debug"
        }
        release {
            resValue "string", "app_name", "My App Name"
        }
    }
}

You can access them in the usual way with @string/app_name or R.string.app_name

Caslon answered 19/6, 2013 at 21:1 Comment(20)
Awesome, is it possible to access these fields from the configs? Such as strings.xml?Taster
Nope, but you can generate resources as well. I've updated my answer including that.Caslon
Awesome, thanks. Something that I have discovered is well is that you could specify alternate directories for the debug and release builds. In <project>/src/, if you create the file debug/res/values/strings.xml and another file release/res/values/strings.xml, you could set resources for the debug and release builds in a slightly cleaner manner as well.Taster
@Caslon is it possible to achieve the same without the android plugin? i.e. just using apply plugin java? thanks!Geraldo
@Geraldo ..I've added an answer that does it using a system property. It does however rely on the 'test' task to pass the property to the application at runtime, but I'm assuming that's ok? Your deployed application would then rely on a system property (vs e.g., a config file).Chopin
Is it possible to set the buildConfigField to a value declared in local.properties?Carboloy
Yes, it is. Combine the answer I gave here: https://mcmap.net/q/81282/-how-do-i-read-properties-defined-in-local-properties-in-build-gradleCaslon
Only annoying thing about generating the resource app_name at build time is that Android Studio will complain in the sidebar about being unable to resolve the resource when editing the manifest, and in graphical previews of layouts that use that as a string.Striped
There is an open issue for this: code.google.com/p/android/issues/detail?id=73595Caslon
How can I create constants for different build flavors and build types?Liaotung
the escaped quotes for the String version are NOT a type-oQuasijudicial
What if you wish to have debug flavour for libraries? As I've noticed, all libraries are marked as "release", as shown here: https://mcmap.net/q/35724/-buildconfig-debug-always-false-when-building-library-projects-with-gradle/878126Sardella
You can use the approach described in the same question: https://mcmap.net/q/35724/-buildconfig-debug-always-false-when-building-library-projects-with-gradle. Personally I wouldn't recommend it since it makes the build process much slower. IHMO (and it's what I did in my project) for a library it better to handle those configurations as runtime and not build configurations.Caslon
Note that string values must be double quoted like in example above: buildConfigField "String", "FOO_STRING", "\"bar\"" (or '"bar"').Parada
Is it possible to set one of the fields, as the current year, and also reach it no matter which build type is chosen (release, debug, ...) ?Sardella
Place it in the defaultConfig block. For the current year, just checkout the Groovy api for handling dates.Caslon
My suggestion is to use "Plugin version greater than 0.7.x" approach. Because it will generate java constant(no need of Context to access constant),But in the case of second approach we need Context everywhere to access resource.Levulose
note: when you use resValue the value can accidentally be overridden by the strings resource file (e.g. for another language) - to get a constant that you can access from java and use in the manifest see this answerPolite
I want a variable to be declared only once, without type of build...Glissade
Be aware that parameters wont be visible until you "make project". "gradle sync" is not enough.Fovea
A
130

An example of usage an Api App Key in an Android application (Java and XML)

gradle.properties

AppKey="XXXX-XXXX"

build.gradle

buildTypes {
//...
    buildTypes.each {
        it.buildConfigField 'String', 'APP_KEY_1', AppKey
        it.resValue 'string', 'APP_KEY_2', AppKey
    }
}

Usage in java code

Log.d("UserActivity", "onCreate, APP_KEY: " + getString(R.string.APP_KEY_2));

BuildConfig.APP_KEY_1

Usage in xml code

<data android:scheme="@string/APP_KEY_2" />
Apetalous answered 26/2, 2016 at 11:21 Comment(3)
If I may add, this variable can be passed on runtime as well. Mostly useful when running tests with different configuration. Use ./gradlew -PAppKey="1234" testdebugTann
To declare the same property for each build type you can use the defaultConfig block as well: https://mcmap.net/q/80085/-is-it-possible-to-declare-a-variable-in-gradle-usable-in-javaCaslon
Do you have a working example of the XML part? in a Github repository or Gist. It's not working for me, I can't reference @string/APP_KEY_2Stereotropism
C
33

Example using system properties, set in build.gradle, read from Java application (following up from question in comments):

Basically, using the test task in build.gradle, with test task method systemProperty setting a system property that's passed at runtime:

apply plugin: 'java'
group = 'example'
version = '0.0.1-SNAPSHOT'

repositories {
    mavenCentral()
    // mavenLocal()
    // maven { url 'http://localhost/nexus/content/groups/public'; }
}

dependencies {
    testCompile 'junit:junit:4.8.2'
    compile 'ch.qos.logback:logback-classic:1.1.2'
}

test {
  logger.info '==test=='
  systemProperty 'MY-VAR1', 'VALUE-TEST'
}

And here's the rest of the sample code (which you could probably infer, but is included here anyway): it gets a system property MY-VAR1, expected at run-time to be set to VALUE-TEST:

package example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  static final Logger log=LoggerFactory.getLogger(HelloWorld.class);
  public static void main(String args[]) {
    log.info("entering main...");
    final String val = System.getProperty("MY-VAR1", "UNSET (MAIN)");
    System.out.println("(main.out) hello, world: " + val);
    log.info("main.log) MY-VAR1=" + val);
  }
}

Testcase: if MY-VAR is unset, the test should fail:

package example;
...
public class HelloWorldTest {
    static final Logger log=LoggerFactory.getLogger(HelloWorldTest.class);
    @Test public void testEnv() {
        HelloWorld.main(new String[]{});
        final String val = System.getProperty("MY-VAR1", "UNSET (TEST)");
        System.out.println("(test.out) var1=" + val);
        log.info("(test.log) MY-VAR1=" + val);
        assertEquals("env MY-VAR1 set.", "VALUE-TEST", val);
    }
}

Run (note: test is passing):

$ gradle cleanTest test
:cleanTest
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test

BUILD SUCCESSFUL

I've found that the tricky part is actually getting the output from gradle... So, logging is configured here (slf4j+logback), and the log file shows the results (alternatively, run gradle --info cleanTest test; there are also properties that get stdout to the console, but, you know, why):

$ cat app.log
INFO Test worker example.HelloWorld - entering main...
INFO Test worker example.HelloWorld - main.log) MY-VAR1=VALUE-TEST
INFO Test worker example.HelloWorldTest - (test.log) MY-VAR1=VALUE-TEST

If you comment out "systemProperty..." (which, btw, only works in a test task), then:

example.HelloWorldTest > testEnv FAILED
    org.junit.ComparisonFailure at HelloWorldTest.java:14

For completeness, here is the logback config (src/test/resources/logback-test.xml):

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>app.log</file>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d %p %t %c - %m%n</pattern>
        </layout>
 </appender>
 <root level="info">
     <appender-ref ref="FILE"/>
</root>
</configuration> 

Files:

  • build.gradle
  • src/main/java/example/HelloWorld.java
  • src/test/java/example/HelloWorldTest.java
  • src/test/resources/logback-test.xml
Chopin answered 7/9, 2014 at 20:44 Comment(4)
Note that this is a direct response to a comment in the accepted answer, so it deviates a little from the original question.Chopin
Can I somehow get version = '0.0.1-SNAPSHOT' via Java code?Sinking
SystemProperty is only available in gradle test task :( . Anybody knows any other way to have gradle variable value in the java code of the library?Xanthin
systemProperty really only makes sense for testing, so I see why they did it this way (it's not an oversight), but at the same time, I've also tried to use gradle for things it wasn't intended for (like an application DSL) so I can identify. As an alternative, I'd recommend just loading properties from a property file (or config service, etc), because if it's not in "test" mode, then it's "production" mode & requires application logic. (That's the theory, anyway.)Chopin
S
16

You can create build config field overridable via system environment variables during build:

Fallback is used while developing, but you can override the variable when you run the build on Jenkins or another tool.

In your app build.gradle:

buildTypes {
        def serverUrl =  '\"' + (System.getenv("SERVER_URL")?: "http://default.fallback.url.com")+'\"'
        debug{
            buildConfigField "String", "SERVER_URL", serverUrl
        }
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            buildConfigField "String", "SERVER_URL", serverUrl
        }
    } 

The variable will be available as BuildConfig.SERVER_URL.

Spriggs answered 14/3, 2017 at 10:28 Comment(2)
Thank you for this answer! I've been trying to work out how to get an environment variable to be visible from within an Android .java file, and this worked great!Marymarya
If you want to define boolean variable, you should use buildConfigField "boolean", "CI_BUILD", "${isCi}" or buildConfigField "boolean", "CI_BUILD", "Boolean.parseBoolean(" + '"' + isCi + '"' + ")" if you want to escape lint checks (#29889598)Ito
L
15

rciovati's answer is entirely correct I just wanted to add one more tidbit that you can also create variables for every build type within the default config portion of your build.gradle. This would look like this:

android {
    defaultConfig {
        buildConfigField "String", "APP_NAME", "\"APP_NAME\""
    }
}

This will allow you to have access to through

BuildConfig.App_NAME

Just wanted to make a note of this scenario as well if you want a common config.

Lubeck answered 25/7, 2018 at 14:14 Comment(0)
C
4

I'm using this code and working very fine.

def baseUrl = '\"http://patelwala.com/myapi/"'
def googleServerKey = '\"87171841097-opu71rk2ps35ibv96ud57g3ktto6ioio.apps.googleusercontent.com"'
android {
  buildTypes {
  release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        buildConfigField 'String', 'BASE_URL', baseUrl
        buildConfigField 'String', 'web_client_id', googleServerKey
    }
    releasedebug {
        initWith debug
        buildConfigField 'String', 'BASE_URL', baseUrl
        buildConfigField 'String', 'web_client_id' ,googleServerKey
    }
    debug {

        buildConfigField 'String', 'BASE_URL', baseUrl
        buildConfigField 'String', 'web_client_id', googleServerKey
    }
 }
}

}

Chumley answered 1/11, 2018 at 8:39 Comment(1)
It would be nice if you specify what you modified and what impact it has, resulting in your working solution.Bluepencil
D
4

None of the above answers gave me any guidelines so I had to spend two hours learning about Groovy Methods.

I wanted be able to go against a production, sandbox and local environment. Because I'm lazy, I only wanted to change the URL at one place. Here is what I came up with:

 flavorDimensions 'environment'
    productFlavors {
        production {
            def SERVER_HOST = "evil-company.com"
            buildConfigField 'String', 'API_HOST', "\"${SERVER_HOST}\""
            buildConfigField 'String', 'API_URL', "\"https://${SERVER_HOST}/api/v1/\""
            buildConfigField 'String', 'WEB_URL', "\"https://${SERVER_HOST}/\""
            dimension 'environment'
        }
        rickard {
            def LOCAL_HOST = "192.168.1.107"
            buildConfigField 'String', 'API_HOST', "\"${LOCAL_HOST}\""
            buildConfigField 'String', 'API_URL', "\"https://${LOCAL_HOST}/api/v1/\""
            buildConfigField 'String', 'WEB_URL', "\"https://${LOCAL_HOST}/\""
            applicationIdSuffix ".dev"
        }
    }

Alternative syntax, because you can only use ${variable} with double quotes in Groovy Methods.

    rickard {
        def LOCAL_HOST = "192.168.1.107"
        buildConfigField 'String', 'API_HOST', '"' + LOCAL_HOST + '"'
        buildConfigField 'String', 'API_URL', '"https://' + LOCAL_HOST + '/api/v1/"'
        buildConfigField 'String', 'WEB_URL', '"https://' + LOCAL_HOST + '"'
        applicationIdSuffix ".dev"
    }

What was hard for me to grasp was that strings needs to be declared as strings surrounded by quotes. Because of that restriction, I couldn't use reference API_HOST directly, which was what I wanted to do in the first place.

Divisibility answered 24/6, 2019 at 10:27 Comment(0)
I
3

I'm using

buildTypes.each {
    it.buildConfigField 'String', 'GoogleMapsApiKey', "\"$System.env.GoogleMapsApiKey\""
}

Its based on Dennis's answer but grabs its from an environment variable.

Iolenta answered 21/10, 2018 at 18:55 Comment(0)
A
3

How can you insert String result of function into buildConfigField

Here's an example of build date in human-readable format set:

def getDate() {
    return new SimpleDateFormat("dd MMMM yyyy", new Locale("ru")).format(new Date())
}

def buildDate = getDate()

defaultConfig {
    buildConfigField "String", "BUILD_DATE", "\"$buildDate\""
}
Ables answered 8/2, 2019 at 15:16 Comment(0)
F
3

This is for Kotlin DSL (build.gradle.kts) usable both in Java and Kotlin:

buildTypes {
    getByName("debug") { // or simply  debug {  in newer version of Android Gradle Plugin (AGP)
        buildConfigField("Boolean", "isHappy", "true")
        buildConfigField("String", "favoriteSong", """"Black Forest"""")
        resValue("string", "myName", "Bunny")
    }
}
Flemish answered 25/1, 2022 at 17:2 Comment(0)
S
2

https://mcmap.net/q/80085/-is-it-possible-to-declare-a-variable-in-gradle-usable-in-java Answer by @rciovati works

But make sure you rebuild the project to be able to remove the error from Android Studio IDE

I spent 30 minutes trying to figure out why the new property variables aren't accessible.

If the "Make Project" as marked with red color doesn't work then try the "Rebuild Project" Button as marked with green color.

enter image description here

Selfcentered answered 8/2, 2021 at 6:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.