Why do you need Lint in Android

In android studio, there is a tool that helps to improve project source files and optimize the performance of correctness-checking, check potential bugs, security, usability and accessibility.

As much as Android testing is importance to the performance of the core functionality, so is the Android Linting which helps to ensure that there is no poorly structured codes that can impact the performance and maintenance of the codebase. For example, in XML, when there is unused namespace in xml file, which takes up space and incurs unnecessary processing. When there is deprecated elements or API calls that are not supported by the target version, might lead to code failing to run correctly.

You can watch the below videos on YouTube and it teaches everything about Linting and more in this blog. The video was broken down into three parts.

a. Missing translation ( and unused translations)
b. Layout performance problems ( all the issues the old layoutopt tools used to find, and more)
c. Unused resources
d. Inconsistent array sizes that is when arrays are defined in multiple configurations)
e. Accessibility and internationalization problems (hardcoded strings and missing contentDescriptions etc)
f. Icon problems (like missing densities, duplicate icons, wrong sizes, etc)
g. Usability problems (like not specifying an input type on a text field)
h. Manifest errors

Android studio comes out with a scanning tool can that detect and correct problems with the structural quality of your code without trying to build or run a test on your app. All errors detected by the lint tool is reported with a description and severity level of the problem. The severity level of the problem can be raised or lowered based on the problem at hand.

Figure 1. Code scanning workflow with the lint tool. Android Developer website

Lint can be configured in our project in android studio. We can add lint inspection in a form of xml like lint.xml or we can select some issues that lint can check from the Code Inspection menu in android studio.

Manual Configuration of Lint: Lint Checks can be configured manually in android studio by going to following this file path, Files > Settings > Editor > Inspections. Now you can select or remove issues you want lint to check for you in your project.

Now after selecting or removing the lint you want to check then you click on Apply button at the bottom far end of the popup and click on OK button. Now your lint is configured manually.After go to Analyze > Inspect Code and select inspection scode and click on OK button to start inspecting your scope file with the lint.

Configuring lint through xml

Lint can be configured by creating a lint.xml file in the root directory of the Android project. Add a list of issues you want lint tool to check. The lint.xml file consists of an enclosing <lint> parent tag that contains one or more children <issue> elements. Lint defines a unique id attribute value for each <issue>. You can change an issue’s severity level or disable lint checking for the issue by setting the severity attribute in the <issue> tag.

Note: To see the lint-supported issues and its corresponding issue id, you can run the lint --list command in your terminal.

android {  lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
lintConfig file('lint.xml')
}
}

The lint xml file is therefore configured in the gradle through the use of lintOptions.

Configuring Lint Checking in Java files

Linting can be disabled in java files by adding the @SuppressLint annotation to that code. For example, how you can turn off lint checking for the NewApi issue in the onCreate method. The lint tool continues to check for the NewApi issue in other methods of this class.

@SuppressLint("NewApi")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

To suppress checking for all lint issues in the file, use the all keyword, like this:

@SuppressLint("all")

Configuring Lint Checking in Xml files

You can use the tools:ignore attribute to disable lint checking for specific sections of your XML files. Put the following namespace value in the lint.xml file so the lint tool recognizes the attribute:

namespace xmlns:tools="http://schemas.android.com/tools"

The following example shows how you can turn off lint checking for the UnusedResources issue in the <LinearLayout> element of an XML layout file. The ignore attribute is inherited by the children elements of the parent element in which the attribute is declared. In this example, the lint check is also disabled for the child <TextView> element.

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="UnusedResources" >

<TextView
android:text="@string/auto_update_prompt" />
</LinearLayout>

To disable more than one issue, list the issues to disable in a comma-separated string. For example:

tools:ignore="NewApi,StringFormatInvalid"

To suppress checking for all lint issues in the XML element, use the all keyword, like this:

tools:ignore="all"

Creating warning Baseline

Baseline is very important when one is working on a project which can be big in future. Baseline helps you to have a snapshot of the current lint warnings in the project. With the baseline set or configured, only warnings or errors after the baseline will be thrown or checked by lint. The baseline snapshot lets you start using lint to fail the build without having to go back and address all existing issues first.

To create a baseline snapshot, modify your project’s build.gradle file as follows.

android {
lintOptions {
baseline file("lint-baseline.xml")
}
}

When you first add this line, the lint-baseline.xml file is created to establish your baseline. From then on, the tools only read the file to determine the baseline. If you want to create a new baseline, manually delete the file and run lint again to recreate it.

Then, run lint from the IDE (Analyze > Inspect Code) or from the command line as follows. The output prints the location of the lint-baseline.xml file. The file location for your setup might be different from what is shown here.

In command line, you can run gradlew lintDebug in your terminal and if your build is successful without errors, then you will get the location where the baseline.xml file is created.

Assuming we are managing a project for a company and within our application source code, we have a number of custom views we have written and the team would want to use that custom views in place of normal android widgets. We can write a custom lint to check that the team uses the custom views instead of any different android widgets. This is helpful as the source code would look like one person writing all the project source code. The uniformity and consistency is maintained.

Before we could start writing our custom test, we need to download lint apis that will give us a helper libraries to write our custom lint. In your app gradle, put these there. And it is a java library, so you need to remove the android configuration.

Now let’s assume we have a custom view already that has been approved by the team. The custom view replaces the android widget, EditText. So we write a lint that cautions the team whenever using normal android EditText in our project.

To begin with we create a detector class and we can call it EditTextDetector.class and it is made to extend LayoutDetector.

public class EditTextDetector extends LayoutDetector {


}

Let’s make our detector to be called only when we declare an edittext in our resource xml file.

public class EditTextDetector extends LayoutDetector {


@Nullable
@Override
public Collection<String> getApplicableElements() {
return Collections.singletonList(SdkConstants.EDIT_TEXT);
}


}

The getApplicableElements method returns a collection of elements we would like lint framework to apply the custom lint to. In this method, we are returning only EditText, which means the lint detection is scoped to only EditText call in the layout file.

Now, we can create Issue. Issue objects helps to declare the following:

  1. Unique id: This helps to identify a particular issue and it has to be unique. If we want to suppress our issue at a point in time, we need this unique id to help us to that.
  2. Brief Description: This is just the title of the issue, this normally shown in the IDE’s preference dialog, as category header in the analysis result window etc.
  3. Explanation: This is the full description or explanation of the issue. Here you can use some of the markup such as `monospace`, *italic*, and **bold**.
  4. Category: This is where you specify the category of the issue. For example, it can be CORRECTNESS, I18N, ICONS, INTEROPERABILITY, PERFORMANCE etc.
  5. Priority: This is how important your issue is. The range is mostly between 1–10.
  6. Severity: This is self explanatory and it can include FATAL, WARNING, INFORMATIONAL, ERROR, IGNORE etc.
  7. Implementation: This is for analysing one or more issues and it takes the detector class which finds the issue and the scope of the files required to analyse the issue.

We create our class that holds the detector information.

public static class EditTextDetectorInfo {
public static String EDIT_TEXT_ID = "CustomEditTextID";
public static String EDIT_TEXT_USAGE = "EditText Usage: Use the team custom view like **FormInputTextHorizCV**";
public static String EDIT_TEXT_EXPLANATION = "According to the business rules, we use \n" +
" **FormInput Custom views** in place of the android widget.\n\n" +
"This helps to maintain consistency in our code base and implementations";

public static Implementation EDIT_TEXT_IMPLEMENTATION = new Implementation(EditTextDetector.class, Scope.RESOURCE_FILE_SCOPE);

}

Now we can create our issue.

public class EditTextDetector extends LayoutDetector {//Creating the issue
public final Issue EDITTEXT_ISSUE = Issue.create(
// ID: used in @SuppressLint warnings etc
EditTextDetectorInfo.EDIT_TEXT_ID,
// Title -- shown in the IDE's preference dialog, as category headers in the
// Analysis results window, etc
EditTextDetectorInfo.EDIT_TEXT_USAGE,
// Full explanation of the issue; you can use some markdown markup such as
// `monospace`, *italic*, and **bold**.
EditTextDetectorInfo.EDIT_TEXT_EXPLANATION,
Category.CORRECTNESS,
10,
Severity.FATAL,
EditTextDetectorInfo.EDIT_TEXT_IMPLEMENTATION
);

@Nullable
@Override
public Collection<String> getApplicableElements() {
return Collections.singletonList(SdkConstants.EDIT_TEXT);
}

public static class EditTextDetectorInfo {
public static String EDIT_TEXT_ID = "CustomEditTextID";
public static String EDIT_TEXT_USAGE = "EditText Usage: Use the team custom view like **FormInputTextHorizCV**";
public static String EDIT_TEXT_EXPLANATION = "According to the business rules, we use \n" +
" **FormInput Custom views** in place of the android widget.\n\n" +
"This helps to maintain consistency in our code base and implementations";

public static Implementation EDIT_TEXT_IMPLEMENTATION =
new Implementation(EditTextDetector.class, Scope.RESOURCE_FILE_SCOPE);

}
}

After the issue is created, we can now create a report that will fire when the detection is triggered. This will fire a message to the developer when EditText is used in the layout file. The report is created in the visitElement() method hook.

@Override
public void visitElement(@NotNull XmlContext context, @NotNull Element element) {
context.report(
EDIT_TEXT_ISSUE,
context.getElementLocation(element),
EditTextDetectorInfo.EDIT_TEXT_MESSAGE,
createQuickFix());
}

In creating the report, we can suggest to the developer of what view to replace with and this is done below.

private LintFix createQuickFix(){
return LintFix.create()
.replace()
.text(SdkConstants.EDIT_TEXT)
.with(EditTextDetectorInfo.CUSTOM_EDIT_TEXT)
.build();
}

Now so putting everything together, we get the class below.

Now we need to register our detector in Lint framework. Issue Registry has two main methods which has to be implemented by the class.

  1. getIssues(): This is where we register the issues we have created already. In our case, we have only one issue so we register it.
  2. getApi(): This returns the current version of the lint framework. So you can return the constant in ApiKt.CURRENT_API.

Now for the framework to pick our registry, we need to create manifest entry about our registry in the build.gradle file. Add below to the gradle file.

jar {
manifest {
attributes("Lint-Registry-v2": "com.bisapp.mycusomlint.EditTextRegistry")
}
}

The full build.gradle file should look like this.

We are done now, we just need to add the lint to our app module to see it in action. To add the custom lint library, just use lintChecks to add it like it is done below.

dependencies {//add this line to the gradle file in the app module.
lintChecks project(":mycusomlint")

}

Below is the result you should get.

In conclusion, Linting can be helpful and reduce stress if you are managing a team and you want the team to use some existing tools, then linting is a solution to enforce this tools to the team. This is a stepping stone and I challenge you to do more stuff with it. Thank you for staying up for this tutorial. 😍

Follow me on my youTube channel for more on android tutorials.

Senior Android Developer at ECOM Trading