Let’s talk about the elephant in the room… Gradle…

To be honest, I hated Gradle. It was always one of the biggest pain points in my carrier as a developer. Whenever I ran into a „Gradle issue“ during Android development, I immediately knew: This is going to be a bad day. All I could then do is copy and paste small little snippets from Stackoverflow, not understanding a single bit what they do and hoping they would work.

I did this until some small snippet „solved“ the problem, unknowingly that this will cause some other issue later. It was the circle of „Gradle issues“ and I felt that everyone in the team handled it pretty similar. Talking to other developers at meetups revealed: This was not just us. Almost everyone was doing it exactly like this.

Except, some Gradle wizards. They knew the answers… The knew how to tame the beast and I was absolutely thrilled by their skills! In this article, I would like to share our team’s path to „Gradle Mastery“ and how easily you can become the guy taming the beast.

The true source of all evil

But before we start fixing our problems with Gradle, let’s try to identify what the „true source of all evil“ is. Especially in the Android community, we often see Gradle being blamed for almost everything: Builds are slow: „Oh f_ck Gradle!“. Publishing a library is confusing: „Oh f_ck Gradle!“. There are issues with Databinding: „Oh f_ck Gradle!“. Let’s clarify one thing:

Android-Gradle-Plugin != Gradle. People used to talk about those two interchangeably, especially at times were the version names of both were pretty similar. Many problems we saw in the last years were not particularly Gradle issues. Blaming build times of Android Projects solely onto Gradle seems not fair to me and plays a huge role in the ongoing witch-hunt against Gradle. So that this is out of our way, what is now causing our pain?

Groovy. It’s Groovy.

Please don’t get me wrong: I am not trying to judge Groovy as a language. I never used Groovy for „a real project“, but using it as a Java/Kotlin developer without any hints of the IDE is absolutely terrifying. To be honest: As I started Android development, I thought that build.gradle files just use some kind of „magical markup language“ which I was unable to make sense of.

The lord and savior: Kotlin & the buildSrc module?

But then Kotlin announced that their new Multiplatform Plugin is now supporting the also pretty new Kotlin Gradle DSL and the team wanted to evaluate this technology for our use cases. You can find our results in this blog post. After we forced ourselves to write build files using Kotlin, it suddenly made the click. Suddenly we can just write code, as we are used to, read documentation as we are used to, click into the implementation of functions that we are using, as we are used to. This is a game-changer for us! Suddenly the Gradle ecosystem laid itself out right in front of us and we liked it a lot.

We started to investigate how we could improve our build scripts even more and found something very promising: The „buildSrc“ folder.

It’s basically a plain old Gradle module… with one twist to it! It will be the very first thing that is built and all the code you write in there will be available later in your build.gradle.kts files! This starts to blur the line between what we considered to be „coding“ and „writing build files“ and it’s even much better than the already cool build.gradle.kts because of a better, more fluent IDE support as well as all the additionally gained freedom by „just having a Gradle module“. We can now structure our code arbitrarily, use dependency injection, read properties from files, etc: The sky is the limit!

We also looked at what other people are currently using this awesome buildSrc folder for at the moment and we found several articles about managing dependencies, that we liked a lot!

Those are awesome ideas, but we are convinced that the community needs to go one step further. Build files tend to grow longer and longer over time (some of ours is up to 400 lines of code), while also gaining more and more complexity. Projects using multiple modules might end up with almost pure boilerplate in their build scripts. If one would want to change details in the deployment, one would have to adjust this code in all these files. That’s awful. Maybe we can make build files easier and more maintainable by splitting functionality into smaller units? Maybe we should share some common *.gradle.kts files and include them in our actual build files, as many projects already do? But is this really flexible enough? Will this scale well? Will this even make sense for a single module project? Will this lead you (and your team) to the path of true „Gradle mastery“?

We think there is a much better and easier approach than sharing *.gradle.kts files. Here it is:

The buildSrc plugin

We can extend the great idea of managing dependencies inside the buildSrc folder! Why not treating build logic and the corresponding buildSrc module as a „first-class citizen“ inside your project and moving related logic in there? But how should we structure this code? Will this be just another confusing new concept to learn for every team member? No! Let’s use a concept that every one of you is already familiar with: Plugins! Let’s build a project-specific Gradle plugin! This will provide a basic structure to our build logic while being familiar to use inside our build files.

Please, bear with me! This will be much, much less complicated than you think. Writing your own Gradle plugin will teach you how to love something, that you always thought would be pure evil.

Let me show you an example of how our build files changed using this approach: At the beginning we started with this, a fucking huge file with almost 200 lines of code:

(See full Gist of this)

Using the described approach, we refactored this monster into an understandable piece of code with just 34 lines of code:

(See full Gist of this)

Quite impressive, right? I am still extremely proud seeing this. But what happened here?

What is the new build script doing?

It applies our project specific gradle plugin „com.quickbirdstudios.bluesqaure“ It configures this plugin using the „bluesqaure“ extension It defines its dependencies (since, you this is really module specific)

No boilerplate anymore, whatsoever 🥳 But please, before you think this is complicated, to do: Hear me out! Let me convince you otherwise and show how you can do something like this within 30 minutes, no matter your current experience with Gradle!

Step 1: Setting up your project specific Gradle Plugin

You might already have a buildSrc folder set up for your project, but just in case you don’t have, here is how to do this:

Create a folder inside the root folder of your project called buildSrc

Create a build script for this folder buildSrc/build.gradle.kts like…



Now let’s implement your plugin: BluesquarePlugin in our exemplary case!

You can find further, additional information about „how to build a Gradle Plugin“ here. But it is actually pretty straight-forward!

We need a class (let’s call it BluesqurePlugin in this example) that extends Plugin<Project> :



The apply function will be called, you guessed it, the moment that this plugin will be applied to your project! But how can we apply this plugin to our real build scripts? Which id will be used? Ok. I have to admit, this is the only part that you might not like, but it’s still pretty simple.

In order to define the id of your plugin (the string used later to apply it, eg. com.quickbirdstudios.bluesquare ), we need to create a properties file inside our META-INF 😒 So here is how you would set this id:

Create a file like

buildSrc/src/main/resources/META-INF/gradle-plugins/com.quickbirdstudios.bluesquare.properties

Which contains the fully qualified name of your plugin’s class



This is it, I promise!

Now we can start to apply this plugin to all of our modules:



Which will call the apply function inside of our Plugin!

Step 2: Move your common code into your Plugin

Let’s do this using exemplary Android configuration. When configuring our Android build, we will use the DSL directly in our build script.



As explained previously,this android {} block is called „Extension“ . The receiver of this function implements AndroidBaseExtensions . Instead of doing this inside each build.gradle.kts file, we could move this into one function that we call when our project plugin gets applied:



After that, creating a new Android module in our project is fairly simple. In our case, we would just apply the com.quickbirdstudios.bluesquare plugin to configure our Android build accordingly:



Note: You can still override properties which are set by our project plugin, by just configuring them again using the android {} block!

Step 3: Apply default dependencies

In the example above, we can see the following dependencies to be applied:

Kotlin Standard library

JUnit

Support Test Runner

Espresso

Since we want to apply these dependencies to all of our modules, we could as well let the plugin do this for us. Let’s add another function configureDependencies() to our plugin which will add dependencies accordingly.



Suddenly, Gradle feels more like a framework or library that you can work with instead of this magical build system, that no one really understands! Creating a new Android Module is now as simple as:

Step 4: Apply default plugins

Let’s face it: Our current implementation has one flaw: If we would apply our bluesquare plugin before the Android plugin, it would not work, since it expects the Android plugin to be loaded already. Also, if all of our modules will be Android modules, why not just let the plugin manage it for us?



Now let’s see how easy it is to create a new Android module:



That’s all. This will automatically apply the Android Library Plugin, configure it and set default dependencies!

But what we want to have pure java modules, that do not load the Android plugin? Here comes the best part of this approach: It’s just coding using a library (Gradle)! You can do whatever you want! You are not even limited in the number of plugins you create! Why not creating a base plugin and creating many sub-classes like BluesquareBasePlugin , BluesquareAndroidPlugin , BluesquareJavaPlugin , BluesquareMultiplatformPlugin . You can do whatever you want and it just feels like coding. The Gradle API’s are actually surprisingly nice to work with!

Make your plugin configurable

Now here is how you can make this approach ultimately flexible! Of course, you have already noticed that you are able to use some „top-level configuration“ functions inside your build script after applying a plugin. For example: After you applied the Android Library or Android Application plugin, you can use the android {} block to configure it.



This concept is called „Extension“ by Gradle and the API’s providing our custom one is pretty simple!

Let’s demonstrate this by an example. Imagine we want to configure two things

Is this module even published? What will be the name of the package (when published)?

Here is what we would like to write inside our build files:



It’s actually as simple as creating a plain old class containing this information:



The only thing left to do is, that we need to tell Gradle about this configurable class:



You will then see this bluesquare extension being configured during the evaluation of your build script! You are, again, unlimited in what you can do with this mechanism! You can create multiple extensions, to configure separate parts of your builds, you could add listeners to fields of this extensions and act on the Project accordingly!

Conclusion: Anyone can become a Gradle wizard

We think buildSrc plugin approach is great! Using this we, at QuickBird Studios, can do the following in just 7 lines of code:

Configure our Android builds

Apply default plugins

Apply default dependencies

Run a custom linter

Run aggregated coverage reports

Run aggregated test result reports

Install default git hooks into the project

Setup Multiplatform builds

Setup publications for our library modules

Deploy libraries to the correct repository (Snapshot/Release)

Call me Donald Trump, but using a buildSrc plugin approach is the best way to organize your builds! It’s superior to creating shared *.gradle.kts files that will be loaded by other build files because of the better IDE support, flexibility and configurability and the overall freedom that this approach gives the developer.

We treat the buildSrc module as a first-class citizen in our project. Anyone could become a Gradle wizard and tame the beast, using this technique. You should finally tame it too.

Are you an Android Developer?

Do you want to work with people that care about good software engineering?

Join our team in Munich