... or at least why Android Lint is crashing
In my last post I showed you how to convert a BehaviorRelay
into a PublishRelay
. This week I have a very special post with the answer to the Ultimate Question — why is Android Lint crashing on some files?
Last week I refactored some code and discovered that my PR builds were failing on our continuous integration server due to a lint error. Upon closer inspection, I discovered the following error:
./gradlew lintDebug
> Task :app:lintDebug FAILED
java.lang.NullPointerException
at com.intellij.psi.impl.compiled.ClsFileImpl.getMirror(ClsFileImpl.java:345)
at com.intellij.psi.impl.compiled.ClsElementImpl.getMirror(ClsElementImpl.java:159)
at com.intellij.psi.impl.compiled.ClsElementImpl.getText(ClsElementImpl.java:202)
at com.intellij.psi.impl.source.resolve.graphInference.InferenceSession.argConstraints(InferenceSession.java:1863)
.
.
.
(stack trace abbreviated for this article)
FAILURE: Build failed with an exception.
* What went wrong:
Errors found:
/Users/matthew.b.groves/StudioProjects/LintErrorExample/app/src/main/java/com/mpeng3/linterrorexample/MainActivity.java: Error: Unexpected failure during lint analysis of MainActivity.java (this is a bug in lint or one of the libraries it depends on)
The crash seems to involve the detector com.android.tools.lint.checks.ApiDetector.
You can try disabling it with something like this:
android {
lintOptions {
disable "InlinedApi", "ObsoleteSdkInt", "Override", "NewApi", "UnusedAttribute"
}
}
Whoa — I had never seen this before! Initially I just ignored this by adding it to our lint-baseline.xml
file (I didn’t want to ignore the all-important ApiDetector lint check — if you’re not familiar with it, go check it out!), but with some prompting from a teammate I dug in a little deeper to figure out what was causing this.
My teammate had a suspicion about what might be causing this, but to get to the bottom of this and figure out exactly what was causing the error I began by commenting out all of the lines of code I had just refactored and started uncommenting them and re-running lint incrementally until I uncovered which line of code was the source of the error:
I finally narrowed it down — it was the code on line 16. However, this is not the whole contents of this class. The code above on its own is fine. However, if you add the following method to your class, you’ll see the issue:
What are we looking at here? Well, we’re seeing an overloaded version of our static method foo
that has a different signature. This code will compile, but lint runs into problems when trying to check whether any of the contained code uses Android APIs that aren’t supported by the version(s) of Android our app is targeting.
You may also be wondering, what is that slightly strange syntax in line 16 of the first example? Java calls this a Method Reference, and it was first introduced as part of Java 8’s support for lambda functions. This is a simpler way of writing the following lambda:
foo(() -> getIntSingle()).subscribe();
Method references are a little nicer to read, right? I also think it’s easier to not have to type the parentheses and arrow. And, Android Studio will even show you a lint warning telling you could convert this to a method reference. Ironically, the longer version here does not trigger the same lint crash as the method reference version.
So, where do we go from here? This is clearly a bug in lint, and I don’t want to have to ignore lint checks in this file. The good news is, we have a couple of different options:
The easiest solution is to rename our
foo
methods so they have different names and aren’t overloads of each other. And, as the official Java documentation on methods points out: “Overloaded methods should be used sparingly, as they can make code much less readable.”Convert the occurrence of the
foo
method that takes anRxGetter<T>
to instead take aSingle<T>
directly. In my case, I can do this as I’m interacting with “cold” observables. It seems that lint is getting confused and can’t tell when it should use theRunnable
vs.RxGetter<T>
version offoo
— changing one of them to take aSingle<T>
makes it easier for lint to distinguish between them.Just using the longer lambda syntax (and suppressing those lint warnings).
Wrapping the method reference with some other method call which then wraps it with the longer lambda syntax so you only have to suppress the “convert to method reference” lint warning in a single place in the code, or executing the method reference so you get direct access to the
Single<T>
, etc.
Whatever you choose though, the most important part is to file a bug against Android Studio (I previously wrote a tidbit about filing bugs, but there’s a new process — now the best way is to click “Help->Submit Feedback” from within Android Studio, which will auto-populate the bug ticket with information about your environment). You can view the bug I filed here:
https://issuetracker.google.com/issues/143971679
Please click the link above and ⭐️ this issue if you’d like to see it fixed!
And, if you’d like to see this for yourself, you can check out this sample project I created:
mpeng3 / LintErrorExample
Sample project demonstrating crash in Android Studio ApiDetector
LintErrorExample
This sample project demonstrates a bug in Android Studio's "ApiDetector" lint check that results in a crash.
The line of code that causes the issue is line 24 of MainActivity.java. Commenting it out causes the lint check to operate normally.
Similarly, renaming the foo()
methods so they're all unique also avoids the crash.
It appears the issue is related to the ApiDetector not being able to distinguish between the versions of foo()
which take a Runnable
vs. an RxGetter<T>
when you pass a method reference as an argument.
To see the error, simply run ./gradlew lintDebug
from the command line.
I filed https://issuetracker.google.com/issues/143971679 against Android Studio to track this issue.
Have other ideas about how to resolve this? Leave a comment below! And, please follow me on Medium if you’re interested in being notified of future tidbits.
Interested in joining the awesome team here at Intrepid? We’re hiring!
This tidbit was discovered on November 6, 2019.
Top comments (0)