0

I develop a custom view in Android Studio. This custom view has a custom property. The possible values of this property are declared as an enum-formatted attr in a declare-stylable tag within a resources tag, all that in an xml file (see below).

Even though there are quite some questions (including answers) about accessing xml-defined enums in this forum, none of those actually helps me in solving my concrete problem.

From within the file customview_values.xml:

<declare-styleable name="CustomView">
    <attr name="customViewMode" format="enum">
        <enum name="CUSTOMVIEW_UNDEFINED" value="0" />
        <enum name="CUSTOMVIEW_BLINKING" value="1" />
        <enum name="CUSTOMVIEW_ROLLING" value="2" />
        <enum name="CUSTOMVIEW_PULSING" value="3" />
        <enum name="CUSTOMVIEW_NOISE" value="4" />
    </attr>
</declare-styleable>

You might accept the following circumstances: Whenever an enum's value in this xml file is changed, this change shall propagate consistently to the whole application. There should be no need to have to adapt a programmatical list somewhere else in the code, whenever one changes this xml file, because this would be very error prone (and hence not in the interest of a reasonable developer).

For this reason, I need a solution, where I can get the value of each entry of the above attr list by its name, programmatically (!).

A second application of what I am looking for is testing: I want to write a test, which checks the consistence of the above-defined list, this test necessarily needs to read the respective values by their names from the xml file.

Please help me, my fellow geniuses, you're my only hope!

Coffee
  • 11
  • 5
  • If those names have a consistent naming convention unique to the enum attribute, you could use reflection to iterate through the `R.id` fields for the names, but not the values. Other than that, there's really no existing way to relate those to the attribute name at runtime, AFAIK. At build time, the aapt tool tracks all of those values and their relations, so it can check that a given attribute is assigned a valid value. I'm sure you could write a build task to generate some sort of mapping accessible at runtime, if you'd like. During testing, though, you could simply parse that XML yourself. – Mike M. Sep 11 '19 at 01:30
  • Thank you for the explanation and these three good ideas, Mike M.. How would such reflection look like? How should that build task be designed, and where should it be placed, in order to make such mapping accessible at runtime. (It sounds like the most promissing approach to me.) How can I parse an XML file like the one above from the resources? (This approach might also work very well.) – Coffee Sep 11 '19 at 21:59
  • The reflection is pretty easy, but unless you somehow encode the value into the name, it might be useless for your needs. E.g., `Field[] ids = R.id.class.getDeclaredFields();`, `for (Field f : ids)`, `if (f.getName().startsWith("CUSTOMVIEW_")) {...}`. For the testing parse, someone's got an implementation here already: https://stackoverflow.com/a/41800870. The build task is currently outside of my scope, atm. I've not yet had to write one, but I might do a little research later, if I get some free time. I'd imagine it would end up using a parse routine similar to that one linked. – Mike M. Sep 11 '19 at 23:41
  • Okay, thank you for the suggestion on reflection and for the link to the parsing answer. I wonder whether one of the getters of the class Field would be suited to access the respective value, f.i. getInt(Object obj) (https://developer.android.com/reference/java/lang/reflect/Field#getInt(java.lang.Object)), even though I don't have any clue about the meaning of the argument obj of type Object. – Coffee Sep 12 '19 at 00:12
  • The object passed there would be an instance of the `Class` that the `Field` belongs to. The `R.id` `Field`s are all `static`, though, so you would pass `null`, because they belong to the `Class`, rather than an instance. However, that's not going to return your enum value. That will return the numerical `int` value of that `R.id`. – Mike M. Sep 12 '19 at 00:20
  • Oh, thank you, I had to read it twice. This makes sense. But why will it return the numerical int value of the R.id, will this happen by convention (per definition)? – Coffee Sep 12 '19 at 01:01
  • I want to tell you that I solved my problem differently given the following observation: The core of my question implies the request that an enum's value should be accessible via the respective enum's identifier like one is used to it for string resources (examplewise). This would be a natural behaviour of Android Studio which I, as a User, naively expected to be present as such. – Coffee Sep 12 '19 at 01:01
  • Now, given that, I changed the format of my attr from "enum" to "reference" and saved the constants in a file integers.xml like `0`. When using the custom view, instead of referring to one of the enum identifiers, I refer to one of the integers (f.i. `@integer/CUSTOMVIEW_UNDEFINED`). Even though, this introduces a new potential source for errors: One could reference falsely, f.i. to integers not actually belonging to CUSTOMVIEW. This kind of error is not possible with enums. – Coffee Sep 12 '19 at 01:02
  • This comes closest to the behaviour I expected in the first place. This way, I can access the values from the resources from code, which also works for testing. – Coffee Sep 12 '19 at 01:02
  • Still, thank you for your comments, through which I learned a lot. – Coffee Sep 12 '19 at 01:06
  • "But why will it return the numerical int value of the R.id...?" – Yeah, it's just a basic Java field. All `R.id`s are just `int`s, and all IDs are compiled into `static int` fields in the `R.id` class. Those `int` values are basically arbitrary (though figured with a certain formula), and are determined by the aapt tool at build time. They simply relate a human-readable string name used during design, to a numerical value that is actually used at runtime. The resulting `R.id` has no runtime relation to the enum. Those constructs were there only to aid in design and writing your code. – Mike M. Sep 12 '19 at 01:21
  • Yeah, changing that attribute to take integers would make it simpler to dynamically adjust to new possible values, but it doesn't restrict the assignable values in XML like an enum would, as you've noted. There would be tradeoffs either way, so whichever works better for you. Anyhoo, glad you got something working, and that I could provide a little info. Cheers! – Mike M. Sep 12 '19 at 01:21
  • I want to come back to the class `R.id`. Let me summarize: It is a class containing static integer fields. Any such integer value is obtained via f.getInt(null) where f is the corresponding field. – Coffee Sep 13 '19 at 08:42
  • In view of your last comments, I would say that the actual technical part about my question is that `enum`s are not even compiled into `R.id` class. If they were compiled into the class `R.id`, and this might be kind of a feature request, then my problem would be solved. – Coffee Sep 13 '19 at 08:46
  • I'm not sure if you're wanting me to answer something? Or are you just trying to straighten this out in your own mind? – Mike M. Sep 13 '19 at 09:04
  • Haha, no, I was expecting you to intervene if it was wrong, but apparently it isn't. (I still stumbled over the `getInt`-on-static-`Field`s thing, but now I got it.) I also thought, maybe you have an oppinion on that. – Coffee Sep 13 '19 at 09:11
  • Eh, it would be nice to be able to somehow query the resources to get full info on an enum attribute, but I suspect that the demand isn't very high. Everybody's just used to doing it like they always have. If anyone ever gets around to making a build task like we discussed, that'd be really handy, and would pretty much cover it. – Mike M. Sep 13 '19 at 09:47
  • Might be a good idea, however, I have no clue about making such build task, even though I'd like to know how to do it. – Coffee Sep 14 '19 at 19:14

0 Answers0