Mike Laren
Mike Laren

Reputation: 8188

Resolving resource values in custom lint rule

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?

Upvotes: 12

Views: 926

Answers (3)

Andr&#233; Diermann
Andr&#233; Diermann

Reputation: 2783

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:

    • Phase 1: Scan your resources
    • Scan for your attribute and put it in a list (instead of evaluating it immediately)
    • Scan your dimension values and put them in a list as well
    • Phase 2:
    • override Detector.afterProjectCheck and resolve your attributes by iterating over the two lists filled within phase 1
  • usually 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 :-)

  1. https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java

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 :-)

Upvotes: 2

Murtaza Khursheed Hussain
Murtaza Khursheed Hussain

Reputation: 15336

Assuming xml node after parsing your data, try the following

Element element = null; //It is your root node.
NamedNodeMap attrib = (NamedNodeMap) element;

int numAttrs = attrib.getLength ();

for (int i = 0; i < numAttrs; i++) {
    Attr attr = (Attr) attrib.item (i);

    String attrName = attr.getNodeName ();
    String attrValue = attr.getNodeValue ();

    System.out.println ("Found attribute: " + attrName + " with value: " + attrValue);

}

Upvotes: 0

ajacian81
ajacian81

Reputation: 7589

I believe you're looking looking for getResources().getDimension().

Source: http://developer.android.com/reference/android/content/res/Resources.html#getDimension%28int%29

Upvotes: 0

Related Questions