Help developers with custom Lint rules15 Dec 2015
Last month, I attended a great BarCamp talk at Droidcon Paris from Matthew Compton. It was about writing your own Lint rules. I was really intrigued and wanted to explore a bit more this great subject. Therefore, I came up with this article that aims to share my thoughts and dive into some concrete examples on how to integrate custom rules into your Android project.
If you are an Android developer, I have no doubt you already know what Lint is, but here is a quick reminder:
Lint is a static code analysis tool that checks your Android project source files for potential bugs and optimization improvements
Lint is the one that reminds you when you forgot to call
show() on your
Toast. It is also the one that makes sure you add a
ImageView to support accessibility. Tons of examples like these ones exist. Indeed, Lint can help you on a huge variety of subjects such as: correctness, security, performance, usability, accessibility, internationalization, and so on.
Lint is easy to use since it can be run on any Android project with a simple Gradle task:
It will generate a report about what it found out and classify the issues by category, priority or severity. This report should always be monitored since it is a great way to guarantee code quality and prevent some bugs in your app.
After this quick introduction, I hope that we can now all agree that Lint is a great help to understand some usages of the Android API Framework.
Why would you write your own rules ?
Something that most developers don’t know is that you can write your own Lint rules. There is a couple of use cases where having custom Lint rules can be very useful:
If you are writing a library/SDK and you want to help developers to use it correctly, Lint rules are great since you can easily show them that they are forgetting something or doing something wrong.
If you have a new developer integrating your team, Lint rules can also be a great way to help him respecting your best practices or your naming conventions.
As you might know, I recently joined CaptainTrain Android team. The following examples are based on two Lint rules I implemented for our app. I think it shows perfectly how Lint can ensure that developers follow project code practices.
Let’s get started.
Custom Lint rules must be implemented in a new module. An example of a
build.gradle for this module could be:
As you can see, we need two compile dependencies to implement our custom Lint rules, so make sure you have them. We also need to precise a
Lint-Registry, we will see later what it is but for now remember it is mandatory. Finally we created a small task to help us installing quickly our new Lint rules.
To compile and deploy this module, you will use the following command:
../gradlew clean install.
Now that we configured our module, let’s see how we can code our first rule.
First rule: Attr must always be prefixed
In the CaptainTrain project, we always prefix our attributes by
ct to avoid clashes with other libraries. This can be easily forgotten by new developers (like me), therefore I wrote the following rule:
- The first
appliesTomethod will keep only XML files.
- The second
appliesTomethod will keep only
getApplicableElementsmethod will keep only
getApplicableAttributesmethod will keep only
After this filtering, we need to implement the
visitElement method where our algorithm is very simple. Once we find a
attr XML tag with a
name attribute that does not come from Android neither starts with
ct, we report an
Issue. The issue is declared as follow at the top of the class:
Each parameter is important and mandatory:
AttrNotPrefixedis the id of our lint rule. It must be unique.
You must prefix your custom attr by ctis a brief description.
To avoid clashes, we prefixed all our attrs.is a more detailed explanation.
TYPOGRAPHYis the Category.
5is the priority. It must be between 1 and 10.
WARNINGis the Severity. We chose only
WARNINGbecause code can still be run safely even with this problem.
Implementationis the bridge between the
Detectorthat will find the issue and the
Scoperequired to analyze the issue. In our case, we need to be in at the resource file level to analyze this prefix problem.
As you may think, the code required is quite easy and understandable. You only need to be careful to the scope you are using and the values you enter for your
The result in your Lint report will look like this :
Second rule: Log in production is forbidden
In CaptainTrain app, we wrapped all our
Log calls into a new class. Since in production, logging can be bad for performance and user’s data security, this class aims to disable logging when our
BuildConfig.DEBUG is false. It also helps to format our logs and some other sweet features. This rule looks like this:
As you can see we find the same pattern than before. First, we have two methods
getApplicableMethodNames that will allow us to specify what we are looking for. Then we find the issue and create it. The only difference is that we are not extending
XmlResourceDetector anymore but extending simply
ClassScanner interfaces to handle Java class checks. Well, in fact it did not change that much from the previous rule… Indeed if we look closely to
XmlResourceDetector, it is just a
XmlScanner. So to sum up for any Lint rule, all we need is to extend
Detectorand implements the right
Finally, we also changed the scope of our
Issue and chose
CLASS_FILE_SCOPE. Indeed, to be able to find this issue, we only need to analyse a single Java class file. Sometimes, you will need to analyze several Java class files to raise an issue, so you will need to use
ALL_CLASS_FILES. You can see that choosing your scope is important, so be careful. All scopes are available here
It might not be clear but several issues can be reported in a same
Detector. It is even the right way to do it since we can therefore process all of them in a single pass, hence improving performances.
The result in your Lint report for this second rule will look like this :
We are missing one last thing: Registering ! Indeed, we need to register our newly created issues into the list of all the lint checks processed:
As we can see, it is also very simple, we simply need to extend
IssueRegistry and implements the
getIssues method to return our custom issues. This class must be the same than the one we declared in our
build.gradle at the beginning.
Of course, I just showed two very simple examples but I hope it is now clear that Lint can be very powerful. It is only up to you to write rules that will suit your needs.
We only saw two types of
Scanner but it exists many more such as:
OtherFileScanner, … Explore them and use the right one.
To start writing your own rules, I encourage, first, to read the system Lint rules to help you understand what can be accomplished and the way of doing it. Their source code is available here.
Finally, Lint can be a great tool to help you fixing development mistakes. Use it ! ;)
Find below all materials that helped me: