Understanding Properties and Providers
Gradle provides properties that are important for lazy configuration. When implementing a custom task or plugin, it’s imperative that you use these lazy properties.
Gradle represents lazy properties with two interfaces:
Properties and providers manage values and configurations in a build script.
In this example, configuration is a Property<String> that is set to the configurationProvider Provider<String>.
The configurationProvider lazily provides the value "Hello, Gradle!":
abstract class MyIntroTask : DefaultTask() {
@get:Input
abstract val configuration: Property<String>
@TaskAction
fun printConfiguration() {
println("Configuration value: ${configuration.get()}")
}
}
val configurationProvider: Provider<String> = project.provider { "Hello, Gradle!" }
tasks.register("myIntroTask", MyIntroTask::class) {
configuration.set(configurationProvider)
}
abstract class MyIntroTask extends DefaultTask {
@Input
abstract Property<String> getConfiguration()
@TaskAction
void printConfiguration() {
println "Configuration value: ${configuration.get()}"
}
}
Provider<String> configurationProvider = project.provider { "Hello, Gradle!" }
tasks.register("myIntroTask", MyIntroTask) {
it.setConfiguration(configurationProvider)
}
Understanding Properties
Properties in Gradle are variables that hold values. They can be defined and accessed within the build script to store information like file paths, version numbers, or custom values.
Properties can be set and retrieved using the project object:
// Setting a property
val simpleMessageProperty: Property<String> = project.objects.property(String::class)
simpleMessageProperty.set("Hello, World from a Property!")
// Accessing a property
println(simpleMessageProperty.get())
// Setting a property
def simpleMessageProperty = project.objects.property(String)
simpleMessageProperty.set("Hello, World from a Property!")
// Accessing a property
println(simpleMessageProperty.get())
Properties:
-
Properties with these types are configurable.
-
Propertyextends theProviderinterface. -
The method Property.set(T) specifies a value for the property, overwriting whatever value may have been present.
-
The method Property.set(Provider) specifies a
Providerfor the value for the property, overwriting whatever value may have been present. This allows you to wire togetherProviderandPropertyinstances before the values are configured. -
A
Propertycan be created by the factory method ObjectFactory.property(Class).
Understanding Providers
Providers are objects that represent a value that may not be immediately available. Providers are useful for lazy evaluation and can be used to model values that may change over time or depend on other tasks or inputs:
// Setting a provider
val simpleMessageProvider: Provider<String> = project.providers.provider { "Hello, World from a Provider!" }
// Accessing a provider
println(simpleMessageProvider.get())
// Setting a provider
def simpleMessageProvider = project.providers.provider { "Hello, World from a Provider!" }
// Accessing a provider
println(simpleMessageProvider.get())
Providers:
-
Properties with these types are read-only.
-
The method Provider.get() returns the current value of the property.
-
A
Providercan be created from anotherProviderusing Provider.map(Transformer). -
Many other types extend
Providerand can be used wherever aProvideris required.
Using Gradle Managed Properties
Gradle’s managed properties allow you to declare properties as abstract getters (Java, Groovy) or abstract properties (Kotlin).
Gradle then automatically provides the implementation for these properties, managing their state.
A property may be mutable, meaning that it has both a get() method and set() method:
abstract class MyPropertyTask : DefaultTask() {
@get:Input
abstract val messageProperty: Property<String> // message property
@TaskAction
fun printMessage() {
println(messageProperty.get())
}
}
tasks.register<MyPropertyTask>("myPropertyTask") {
messageProperty.set("Hello, Gradle!")
}
abstract class MyPropertyTask extends DefaultTask {
@Input
abstract Property<String> messageProperty = project.objects.property(String)
@TaskAction
void printMessage() {
println(messageProperty.get())
}
}
tasks.register('myPropertyTask', MyPropertyTask) {
messageProperty.set("Hello, Gradle!")
}
Or read-only, meaning that it has only a get() method.
The read-only properties are providers:
abstract class MyProviderTask : DefaultTask() {
final val messageProvider: Provider<String> = project.providers.provider { "Hello, Gradle!" } // message provider
@TaskAction
fun printMessage() {
println(messageProvider.get())
}
}
tasks.register<MyProviderTask>("MyProviderTask") {
}
abstract class MyProviderTask extends DefaultTask {
final Provider<String> messageProvider = project.providers.provider { "Hello, Gradle!" }
@TaskAction
void printMessage() {
println(messageProvider.get())
}
}
tasks.register('MyProviderTask', MyProviderTask)
Mutable Managed Properties
A mutable managed property is declared using an abstract getter method of type Property<T>, where T can be any serializable type or a fully managed Gradle type.
The property must not have any setter methods.
Here is an example of a task type with an uri property of type URI:
public abstract class Download extends DefaultTask {
@Input
public abstract Property<URI> getUri(); // abstract getter of type Property<T>
@TaskAction
void run() {
System.out.println("Downloading " + getUri().get()); // Use the `uri` property
}
}
Note that for a property to be considered a mutable managed property, the property’s getter methods must be abstract and have public or protected visibility.
The property type must be one of the following:
| Property Type | Note |
|---|---|
|
Where |
|
Configurable regular file location, whose value is mutable |
|
Configurable directory location, whose value is mutable |
|
List of elements of type |
|
Set of elements of type |
|
Map of |
|
A mutable |
|
A mutable |
Read-only Managed Properties (Providers)
You can declare a read-only managed property, also known as a provider, using a getter method of type Provider<T>.
The method implementation needs to derive the value.
It can, for example, derive the value from other properties.
Here is an example of a task type with a uri provider that is derived from a location property:
public abstract class Download extends DefaultTask {
@Input
public abstract Property<String> getLocation();
@Internal
public Provider<URI> getUri() {
return getLocation().map(l -> URI.create("https://" + l));
}
@TaskAction
void run() {
System.out.println("Downloading " + getUri().get()); // Use the `uri` provider (read-only property)
}
}
Read-only Managed Nested Properties (Nested Providers)
You can declare a read-only managed nested property by adding an abstract getter method for the property to a type annotated with @Nested.
The property should not have any setter methods.
Gradle provides the implementation for the getter method and creates a value for the property.
This pattern is useful when a custom type has a nested complex type which has the same lifecycle.
If the lifecycle is different, consider using Property<NestedType> instead.
Here is an example of a task type with a resource property.
The Resource type is also a custom Gradle type and defines some managed properties:
public abstract class Download extends DefaultTask {
@Nested
public abstract Resource getResource(); // Use an abstract getter method annotated with @Nested
@TaskAction
void run() {
// Use the `resource` property
System.out.println("Downloading https://" + getResource().getHostName().get() + "/" + getResource().getPath().get());
}
}
public interface Resource {
@Input
Property<String> getHostName();
@Input
Property<String> getPath();
}
Read-only Managed "name" Property (Provider)
If the type contains an abstract property called "name" of type String, Gradle provides an implementation for the getter
method, and extends each constructor with a "name" parameter, which comes before all other constructor parameters.
If the type is an interface, Gradle will provide a constructor with a single "name" parameter and @Inject semantics.
You can have your type implement or extend the Named interface, which defines such a read-only "name" property:
import org.gradle.api.Named
interface MyType : Named {
// Other properties and methods...
}
class MyTypeImpl(override val name: String) : MyType {
// Implement other properties and methods...
}
// Usage
val instance = MyTypeImpl("myName")
println(instance.name) // Prints: myName
Using Gradle Managed Types
A managed type as an abstract class or interface with no fields and whose properties are all managed. These types have their state entirely managed by Gradle.
For example, this managed type is defined as an interface:
public interface Resource {
@Input
Property<String> getHostName();
@Input
Property<String> getPath();
}
A named managed type is a managed type that additionally has an abstract property "name" of type String.
Named managed types are especially useful as the element type of NamedDomainObjectContainer:
interface MyNamedType {
val name: String
}
class MyNamedTypeImpl(override val name: String) : MyNamedType
class MyPluginExtension(project: Project) {
val myNamedContainer: NamedDomainObjectContainer<MyNamedType> =
project.container(MyNamedType::class.java) { name ->
project.objects.newInstance(MyNamedTypeImpl::class.java, name)
}
}
interface MyNamedType {
String getName()
}
class MyNamedTypeImpl implements MyNamedType {
String name
MyNamedTypeImpl(String name) {
this.name = name
}
}
class MyPluginExtension {
NamedDomainObjectContainer<MyNamedType> myNamedContainer
MyPluginExtension(Project project) {
myNamedContainer = project.container(MyNamedType) { name ->
new MyNamedTypeImpl(name)
}
}
}
Using Java Bean Properties
Sometimes you may see properties implemented in the Java bean property style.
That is, they do not use a Property<T> or Provider<T> types but are instead implemented with concrete setter and getter methods (or corresponding conveniences in Groovy or Kotlin).
This style of property definition is legacy in Gradle and is discouraged:
public class MyTask extends DefaultTask {
private String someProperty;
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}
@TaskAction
public void myAction() {
System.out.println("SomeProperty: " + someProperty);
}
}