I have a large Android codebase and I am writing a custom lint rule that checks whether the values of certain attributes fall within a given range.
For example, I have this component:
<MyCustomComponent
my:animation_factor="0.7"
...>
</MyCustomComponent>
and I want to write a lint rule that alerts developers that values of my:animation_factor
>= 1 should be used with caution.
I followed the instructions at http://tools.android.com/tips/lint-custom-rules and managed to retrieve the value of my:animation_factor
using this code:
import com.android.tools.lint.detector.api.*;
public class XmlInterpolatorFactorTooHighDetector {
....
@Override
public Collection<String> getApplicableElements() {
return ImmutableList.of("MyCustomComponent");
}
@Override
public void visitElement(XmlContext context, Element element) {
String factor = element.getAttribute("my:animation_factor");
...
if (value.startsWith("@dimen/")) {
// How do I resolve @dimen/xyz to 1.85?
} else {
String value = Float.parseFloat(factor);
}
}
}
This code works fine when attributes such as my:animation_factor
have literal values (e.g. 0.7
).
However, when the attribute value is a resources (e.g. @dimen/standard_anim_factor
) then element.getAttribute(...)
returns the string value of the attribute instead of the actual resolved value.
For example, when I have a MyCustomComponent
that looks like this:
<MyCustomComponent
my:animation_factor="@dimen/standard_anim_factory"
...>
</MyCustomComponent>
and @dimen/standard_anim_factor
is defined elsewhere:
<dimen name="standard_anim_factor">1.85</dimen>
then the string factor
becomes "@dimen/standard_anim_factor" instead of "1.85".
Is there a way to resolve "@dimen/standard_anim_factor" to the actual value of resource (i.e. "1.85") while processing the MyCustomComponent
element?
The general problem with the resolution of values is, that they depend on the Android runtime context you are in. There might be several values
folders with different concrete values for your key @dimen/standard_anim_factory
, so just that you are aware of.
Nevertheless, AFAIK there exist two options:
Perform a two phase detection:
Detector.afterProjectCheck
and resolve your attributes by iterating over the two lists filled within phase 1usually the LintUtils
class [1] is a perfect spot for that stuff but unfortunately there is no method which resolves dimensions values. However, there is a method called getStyleAttributes
which demonstrates how to resolve resource values. So you could write your own convenient method to resolve dimension values:
private int resolveDimensionValue(String name, Context context){
LintClient client = context.getDriver().getClient();
LintProject project = context.getDriver().getProject();
AbstractResourceRepository resources = client.getProjectResources(project, true);
return Integer.valueOf(resources.getResourceItem(ResourceType.DIMEN, name).get(0).getResourceValue(false).getValue());
}
Note: I haven't tested the above code yet. So please see it as theoretical advice :-)
Just one more slight advice for your custom Lint rule code, since you are only interested in the attribute:
Instead of doing something like this in visitElement
:
String factor = element.getAttribute("my:animation_factor");
...you may want to do something like this:
@Override
public Collection<String> getApplicableAttributes() {
return ImmutableList.of("my:animation_factor");
}
@Override
void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute){
...
}
But it's just a matter of preference :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With