Chapter 7. Creating Tasks and Plugins

So far, we have been manipulating properties for our Gradle builds and learning how to run tasks. In this chapter, we will get a deeper understanding of those properties, and start creating our own tasks. Once we know how to write our own tasks, we can go even further, and look at how to make our own plugins that can be reused in several projects.

Before we look at how to create custom tasks, we need to learn a few important Groovy concepts. This is because having a basic understanding of how Groovy works makes it a lot easier to get started with custom tasks and plugins. Knowing Groovy also helps to understand how Gradle works, and why the build configuration files look the way they do.

In this chapter, we will look at the following topics:

  • Understanding Groovy
  • Getting started with tasks
  • Hooking into the Android plugin
  • Creating your own plugins

Understanding Groovy

As most Android developers are proficient Java developers, it is interesting to look at how Groovy works compared to Java. Groovy is quite easy to read if you are a Java developer, but writing your own Groovy code would be a hard task without a small introduction.

Tip

A good way to experiment with Groovy is to use the Groovy Console. This application comes with the Groovy SDK and makes it easy to try out Groovy statements while getting an immediate response. The Groovy Console is also able to handle pure Java code, which makes it easy to compare Java and Groovy code. You can download the Groovy SDK, including the Groovy Console, from the Groovy website at http://groovy-lang.org/download.html.

Introduction

Groovy is derived from Java and runs on the Java Virtual Machine. Its goal is to be a simpler, more straightforward language that can be used either as a scripting language or as a full-blown programming language. Throughout this section, we will compare Groovy with Java to make it easier to grasp how Groovy works and to clearly see the difference between both languages.

In Java, printing a string to the screen looks like this:

System.out.println("Hello, world!");

In Groovy, you can accomplish the same with this line of code:

println 'Hello, world!'

You will immediately notice a few key differences:

  • No System.out namespace
  • No parentheses around method parameters
  • No semicolons at the end of a line

The example also uses single quotes around a string. You can use either single quotes or double quotes for strings, but they have different usages. Double-quoted string can also include interpolated expressions. Interpolation is the process of evaluating a string that contains placeholders, and replacing those placeholders with their values. These placeholder expressions can be variables, or even methods. Placeholder expressions that contain a method or multiple variables need to be surrounded by curly brackets and prefixed by $. Placeholder expressions that contain a single variable can just be prefixed with $. Here are some examples of string interpolation in Groovy:

def name = 'Andy'
def greeting = "Hello, $name!"
def name_size "Your name is ${name.size()} characters long."

The greeting variable contains the string "Hello, Andy" and name_size is "Your name is 4 characters long.".

String interpolation allows you to execute code dynamically as well. This example is valid code that prints the current date:

def method = 'toString'
new Date()."$method"()

This looks very strange when you are used to Java, but it is normal syntax and behavior in dynamic programming languages.

Classes and members

Creating a class in Groovy looks a lot like creating a class in Java. Here is an example of a simple class containing one member:

class MyGroovyClass {
    String greeting

    String getGreeting() {
        return 'Hello!'
    }
}

Notice that neither the class nor the member has explicit access modifiers. The default access modifiers in Groovy are different from in Java. Classes themselves are public, just like methods, while class members are private.

To use MyGroovyClass, make a new instance of it:

def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()

You can use the keyword def to create new variables. Once you have a new instance of a class, you can manipulate its members. Accessors are added automatically by Groovy. You can still override them, as we did with getGreeting() in the definition of MyGroovyClass. If you specify nothing, you will still be able to use both a getter and a setter for every member in your class.

If you try to call a member directly, you will, in fact, call the getter. This means you do not need to type instance.getGreeting(), you can just use the shorter instance.greeting instead:

println instance.getGreeting()
println instance.greeting

Both lines in the preceding code sample print out the exact same thing.

Methods

Just like with variables, you do not need to define a specific return type for your methods. You are free to do so anyway, even if it is just for the sake of clarity. Another difference between Java and Groovy methods is that in Groovy, the last line of a method is always returned by default, even without using the return keyword.

To demonstrate the differences between Java and Groovy, consider this Java example of a method that returns the square of a number:

public int square(int num) {
    return num * num;
}
square(2);

You need to specify that the method is publicly accessible, what the return type is, and what the type of the parameter is. At the end of the method, you need to return a value of the return type.

The same method definition looks like this in Groovy:

def square(def num) {
    num * num
}
square 4

Neither the return type, nor the parameter type is explicitly defined. The def keyword is used instead of an explicit type, and the method implicitly returns a value without using the return keyword. However, using the return keyword is still recommended for clarity. When you call the method, you do not need parentheses or semicolon.

There is also another, even shorter, way to define new methods in Groovy. The same square method can also look like this:

def square = { num ->
    num * num
}
square 8

This is not a regular method, but a closure. The concept of closures does not exist in the same way in Java, but it plays a significant role in Groovy and in Gradle especially.

Closures

Closures are anonymous blocks of code that can accept parameters and can return a value. They can be assigned to variables and can be passed as parameters to methods.

You can define a closure simply by adding a block of code between curly brackets, as you saw in the previous example. If you want to be a bit more explicit, you can add the type to your definition, like this:

Closure square = {
    it * it
}
square 16

Adding the Closure type makes it clear to everyone working with the code that a closure is being defined. The preceding example also introduces the concept of an implicit untyped argument named it. If you do not explicitly add a parameter to a closure, Groovy will add one automatically. This parameter is always called it, and you can use it in all closures. If the caller does not specify any parameters, it is null. This can make your code a bit more concise, but it is only useful if the closure takes just one single parameter.

In the context of Gradle, we work with closures all the time. In this book, we have been referring to closures as blocks so far. This means that, for example, the android block and the dependencies block are closures.

Collections

There are two important collection types when using Groovy in a Gradle context: lists and maps.

Creating a new list in Groovy is easy. There is no need for special initializers; you can simply create a list like this:

List list = [1, 2, 3, 4, 5]

Iterating over a list is also extremely easy. You can use the each method to iterate over every element in a list:

list.each() { element ->
    println element
}

The each method enables you to access each element in the list. You can make this code even shorter by using the it variable that was mentioned earlier:

list.each() {
    println it
}

Another type of collection that is important in the context of Gradle is Map. Maps are used in several Gradle settings and methods. A map is, simply put, a list that contains key-value pairs. You can define a map like this:

Map pizzaPrices = [margherita:10, pepperoni:12]

To access specific items in a map, use the get method or square brackets:

pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']

Groovy has a shortcut for this functionality as well. You can use dot notation for map elements, using the key to retrieve the value:

pizzaPrices.pepperoni

Groovy in Gradle

Now that you know the basics of Groovy, it is an interesting exercise to go back, look at a Gradle build file, and read it. Notice that it has become easier to understand why the syntax for configuration looks the way it does. For example, look at the line where the Android plugin is applied to the build:

apply plugin: 'com.android.application'

This piece of code is full of Groovy shortcuts. If you write it out without any of the shortcuts, it looks like this:

project.apply([plugin: 'com.android.application'])

Rewriting the line without Groovy shortcuts makes it clear that apply() is a method of the Project class, which is the basic building block of every Gradle build. The apply() method takes one parameter, which is a Map with a key plugin and value com.android.application.

Another example is the dependencies block. Previously, we defined dependencies like this:

dependencies {
    compile 'com.google.code.gson:gson:2.3'
}

We now know that this block is a closure, passed to the dependencies() method on a Project object. This closure is passed to a DependencyHandler, which contains the add() method. That method accepts three parameters: a string defining the configuration, an object defining the dependency notation, and a closure that contains properties specifically for this dependency. When you write this out in full, it looks like this:

project.dependencies({
    add('compile', 'com.google.code.gson:gson:2.3', {
        // Configuration statements
    })
})

The build configuration files we have been looking at so far should start making a lot more sense, now that you know what it looks like behind the curtains.

Note

If you want to know more about the way Gradle makes use of Groovy under the hood, you can use the official documentation for Project as a starting point. You can find it at http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.103.210