0

Assuming I have a custom attribute my_custom_enum for my views:

<attr name="my_custom_enum" format="enum">
    <enum name="first_value" value="751"/>
    <enum name="second_value" value="2222"/>
    <enum name="third_value" value="1241"/>
    <enum name="fourth_value" value="4"/>
</attr>

<declare-styleable name="CustomViewOne">
    <attr name="my_custom_enum"/>
</declare-styleable>

<declare-styleable name="CustomViewTwo">
    <attr name="my_custom_enum"/>
</declare-styleable>

is there a way to obtain all the possible values of this enum in code?

In other words:

I would like to have a way to obtain values 751, 2222, 1241 and 4. names of those values would be nice too, but are not mandatory.

Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132
  • what do you need it for? btw see how std enums are handled in the framework (typeface, visibility, scaleType etc) – pskink Jan 19 '17 at 12:02
  • @pskink I need it for a unit test. The value of this kind of enum is passed to a different module that sets up the view. I wanted to write a test for this other module, so that I can be sure that all the possible values are taken care of. – Bartek Lipinski Jan 19 '17 at 12:07
  • so you have corresponding `enum` in java rigth? then call `values()` static method on it to get all the values – pskink Jan 19 '17 at 12:16
  • I would then skip the exact link I wanna test. I want to know if there is a corresponding value for every single value from `attr` inside my `enum` in java. – Bartek Lipinski Jan 19 '17 at 12:26
  • + if the specific value from `attr` is used to perform the right actions. – Bartek Lipinski Jan 19 '17 at 12:28
  • ok, i see it now, maybe [this](http://stackoverflow.com/questions/18382559/how-to-get-an-enum-which-is-created-in-attrs-xml-in-code) will help – pskink Jan 19 '17 at 13:19
  • I know the thread. Unfortunately there wasn't anything that would help me. Still, thanks for help. – Bartek Lipinski Jan 19 '17 at 14:13
  • even if you were able to get all the info you want it would be pointless as attribute names are [almost] always different then corresponding enum names: will you have `enum XXX { first_value(751), second_value(2222) [...] }`? i dont think so – pskink Jan 19 '17 at 14:24
  • That's why getting only values, and testing if there is a mapping for every single value would be enough for me. That's why `name` is not really needed. – Bartek Lipinski Jan 19 '17 at 14:53
  • so `enum XX {FIRST(2222), SECOND(751), /* the rest of enums go here */ [...]}` would be ok? notice that "by mistake" i set FIRST=2222 and not 751 as in xml and SECOND=751 and not 2222 as in xml, you have four values in emum and xml but two of them are switched – pskink Jan 19 '17 at 15:02
  • Maybe let me tell it a bit differently: mostly I want to test if there are all values covered. If there is anything returned for any value. That's my primary goal. Testing the proper mapping is secondary. If I could get that as well, it would be awesome, but if I have to choose one or another, the first test would be enough. – Bartek Lipinski Jan 19 '17 at 15:14
  • 1
    So you have to write your own xml parser as the xml enum name/value is not saved anywhere in the final apk, only ints are used when passing `AttributeSet` values – pskink Jan 19 '17 at 15:21
  • Hmmm... that actually sounds interesting. Let me try that – Bartek Lipinski Jan 19 '17 at 18:06
  • @pskink, yea alright I made that work. Why don't you post that as an answer so I can give you dem sweet reputation points :). – Bartek Lipinski Jan 19 '17 at 19:35
  • its you who actually implemented this, so feel free and write how you did it in the answer – pskink Jan 19 '17 at 19:42
  • come on man :D you gave me the idea, you deserve the credit – Bartek Lipinski Jan 19 '17 at 20:13
  • sometimes great ideas are priceless, but really, this is not that one... go for it and writ the answer down... – pskink Jan 19 '17 at 20:35

1 Answers1

1

The solution I ended up with is the one suggested by pskink in the comment: parsing the attrs.xml and extracting values myself.

There are two reasons that made it perfectly reasonable to do it this way:

  1. I need this for a Unit Test (to know a bit more about that, read my conversation with pskink in comments under the question).
  2. Pairs name/value are not stored anywhere. Only ints are used when using AttributeSet .

The code I ended up with is this:

public final class AttrsUtils {

    private static final String TAG_ATTR = "attr";
    private static final String TAG_ENUM = "enum";
    private static final String ATTRIBUTE_NAME = "name";
    private static final String ATTRIBUTE_FORMAT = "format";
    private static final String ATTRIBUTE_VALUE = "value";

    @CheckResult
    @NonNull
    public static Map<String, Integer> getEnumAttributeValues(String attrName)
            throws ParserConfigurationException, IOException, SAXException {
        final File attrsFile = new File("../app/src/main/res/values/attrs.xml");
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(attrsFile);
        doc.getDocumentElement().normalize();

        Map<String, Integer> fontAttributes = new ArrayMap<>();

        NodeList nList = doc.getElementsByTagName(TAG_ATTR);
        for (int temp = 0; temp < nList.getLength(); temp++) {
            Node attrNode = nList.item(temp);
            if (attrNode.getNodeType() == Node.ELEMENT_NODE) {
                Element attrElement = (Element) attrNode;
                final String name = attrElement.getAttribute(ATTRIBUTE_NAME);
                if (!attrElement.hasAttribute(ATTRIBUTE_FORMAT) || !name.equals(attrName)) {
                    continue;
                }

                final NodeList enumNodeList = attrElement.getElementsByTagName(TAG_ENUM);
                for (int i = 0, size = enumNodeList.getLength(); i < size; ++i) {
                    final Node enumNode = enumNodeList.item(i);
                    if (enumNode.getNodeType() == Node.ELEMENT_NODE) {
                        Element enumElement = (Element) enumNode;
                        fontAttributes.put(
                                enumElement.getAttribute(ATTRIBUTE_NAME),
                                Integer.parseInt(enumElement.getAttribute(ATTRIBUTE_VALUE)));
                    }
                }
                break; // we already found the right attr, we can break the loop
            }
        }
        return fontAttributes;
    }

    // Suppress default constructor for noninstantiability
    private AttrsUtils() {
        throw new AssertionError();
    }
}

This method returns a Map of name-value pairs that represent an attribute that has attrName.


For the example I wrote in the question, you would use this method like this:

Map<String, Integer> enumAttr = AttrsUtils.getEnumAttributeValues("my_custom_enum");
Community
  • 1
  • 1
Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132