Why do I get this Exception?
05-18 20:29:38.044: ERROR/AndroidRuntime(5453): java.lang.IllegalArgumentException: The key must be an application-specific resource id.
05-18 20:29:38.044: ERROR/AndroidRuntime(5453): at android.view.View.setTag(View.java:7704)
05-18 20:29:38.044: ERROR/AndroidRuntime(5453): at com.mypkg.viewP.inflateRow(viewP.java:518)
the line in question is:
((Button) row.findViewById(R.id.btnPickContact)).setTag(TAG_ONLINE_ID,objContact.onlineid);
and I have it defined as:
private static final int TAG_ONLINE_ID = 1;
The reason you're not able to use setTag(int, Object) is because android require a pre-compiled unique id in the 'int' argument.
Try creating two unique entry in String.xml xml say, "firstname" & "secondname" & use them as below
imageView.setTag(R.string.firstname, "Abhishek");
imageView.setTag(R.string.lastname, "Gondalia");
I'm a little late to the party but I stumbled on this problem myself today and thought I'd give an answer as well. This answer will be a bit of a compilation of the other answers, but with a twist. First of all, the id, as has been pointed out by others, can NOT be a constant defined in your code (such as private static final int MYID = 123) or any other int that you define as a field somewhere.
The id has to be a precompiled unique id, just like the ones you get for strings that you put in values/strings.xml (ie R.string.mystring). Refer to http://developer.android.com/guide/topics/resources/available-resources.html and http://developer.android.com/guide/topics/resources/more-resources.html for more information.
My suggestion is that you create a new file called values/tags.xml and write:
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="TAG_ONLINE_ID" type="id"/>
</resources>
I think it's better to create a separate file instead of putting it in strings.xml as EtienneSky suggested.
THIS WILL DO THE JOB...
If you just have 1 setTag in your class, you could use any int, maybe static final declared in the top.
The problem comes when you had 2 or more setTag's with different keys.
I mean:
public static final int KEY_1 = 1;
public static final int KEY_2 = 2;
...
setTag(KEY_1, VALUE_1)
setTag(KEY_2, VALUE_2)
...
That scenario is wrong. You then need to add a value file called maybe ids.xml with the following:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="resourceDrawable" />
<item type="id" name="imageURI" />
</resources>
Then, in your class, call:
...
setTag(R.id.resourceDrawable, VALUE_1)
setTag(R.id.imageURI, VALUE_2)
...
The tag id must be unique so it wants it to be an id created in a resources file to guarantee uniqueness.
If the view will only contain one tag though you can just do
setTag(objContact.onlineid);
private static final int TAG_ONLINE_ID = 1 + 2 << 24;
should work. More info from ceph3us:
The specified key should be an id declared in the resources of the
application to ensure it is unique Keys identified as belonging to the
Android framework or not associated with any package will cause an
IllegalArgumentException to be thrown.
from source:
public void setTag(int key, final Object tag) {
// If the package id is 0x00 or 0x01, it's either an undefined package
// or a framework id
if ((key >>> 24) < 2) {
throw new IllegalArgumentException("The key must be an application-specific "
+ "resource id.");
}
setKeyedTag(key, tag);
}
I've used viewHolder.itemTitleTextView.getId(). But you can also declare in your resources:
<item type="id" name="conversation_thread_id"/>
you can use this :
private static final int TAG_ONLINE_ID = View.generateViewId() + 2 << 24;
for uniqness application-specific resource id
This works for me:
setTag(0xffffffff,objContact.onlineid);
The reason why you want to save the value by an id is, that you want to cover more than one value in this tag, right?
Here a more simple solution:
Let's say you want to save two values (Strings) into this tag: "firstname" and "lastname". You can save them both in one string, separated by semicolon:
v.setTag(firstname + ";" + lastname);
... and access them by splitting them into an string array:
String[] data = v.getTag().toString().split(";");
System.out.println(data[0]) //firstname
System.out.println(data[1]) //lastname
Here is a simple workaround that works for me:
int tagKey = "YourSimpleKey".hashCode();
myView.setTag(tagKey, "MyTagObject");
the important clue here is to call .hashCode(); on the String
Related
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:
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 :-)
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 :-)
I believe you're looking looking for getResources().getDimension().
Source: http://developer.android.com/reference/android/content/res/Resources.html#getDimension%28int%29
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);
}
I am trying to add a float to my dimens.xml file.
I was reading the following SO answer. When I tried the solution, I got the exception described in the comments. I am trying to figure out why that exception is thrown.
For completeness here is the XML:
<item name="zoom_level" format="float" type="dimen">15.0</item>
Here is the code that blows up:
final float zoom = this.getResources().getDimension(R.dimen.zoom_level);
I jumped into the Android source, and here is the method definition for getDimension:
public float getDimension(int id) throws NotFoundException {
synchronized (mTmpValue) {
TypedValue value = mTmpValue;
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, mMetrics);
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
So for whatever reason value.type != TypedValue.TYPE_DIMENSION. I do not have my Android source completely set up so I cannot easily add a Log.w("YARIAN", "value type is " + value.type)' statement in there.
I then jumped into getValue and the chain of calls seems to be:
Resources.getValue -> AssetManager.getResourceValue -> AssetManager.loadResourceValue
loadResourceValue is a native method and here is where my digging falls apart.
Anybody know what the best way to understand what's going is?
I also noticed that Resources has a TypedValue.TYPE_FLOAT and TypedValue.TYPE_DIMENSION. But in XML, I cannot write type="float".
The work around described in the comments is to use type=string and then use Float.parse to get the float. Is this necessary? Why or why not?
I know it's a late answer but you should use TypedValue#getFloat() instead of parsing the String to a float like you suggested.
XML:
<item name="float_resource" format="float" type="raw">5.0</item>
Java:
TypedValue out = new TypedValue();
context.getResources().getValue(R.raw.float_resource, out, true);
float floatResource = out.getFloat();
You can put fraction, raw or string as the type if you prefer, this only corresponds to the resource class in R.
There's now Resources.getFloat (from API 29) and ResourcesCompat.getFloat:
val zoomLevel: Float = ResourcesCompat.getFloat(resources, R.dimen.zoom_level)
You can leave your zoom_level XML as it is in the question.
I just ran into this problem too, and though the error message isn't too helpful, I realized my problem was that I was putting just a float value in my resource file and didn't specify a measurement. Switching 15.0 to 15.0dp for instance would avoid the problem and allow you to still use a regular dimension resource.
Kotlin extension function made from Rich answer:
fun Resources.getFloatValue(#DimenRes floatRes:Int):Float{
val out = TypedValue()
getValue(floatRes, out, true)
return out.float
}
Usage:
resources.getFloatValue(R.dimen.my_float)
I have declared few integer values in an xml and need to use the values in a Class to define the array size of an object.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default Object Count -->
<item format="integer" name="item1" type="integer">3</item>
<item format="integer" name="item2" type="integer">1</item>
<item format="integer" name="item3" type="integer">1</item>
</resources>
I am using above values in my class as follows
public class InitialiseObjects {
// For now static number of objects initialized
private String TAG = "INIT_OBJECTS";
int ITEM1_COUNT = R.integer.item1;
int ITEM2_COUNT = R.integer.item2;
int ITEM3_COUNT = R.integer.item3;
private Item1[] item1Objs = new Item1[ITEM1_COUNT];
private Item2[] item2Objs = new Item2[ITEM2_COUNT];
private Item3[] item3Objs = new Item3[ITEM3_COUNT];
}
I expect ITEM*_COUNT to be 3,1,1 respectively for items 1,2,3.
However I get 2131034112, 2131034113, 2131034114 respectively
What's wrong here ?
Android 2.2 [API-8] is being used
R.integer.item1 is the ID of the resource, and thus a very big and arbitrary integer.
The value your looking for is getContext().getResources().getInteger(R.integer.item1);
Thus, you won't be able to get them in a static code.
You should use lazy initialization in your code :
private Item1[] item1Objs;
public Item1[] getItem1Array(Context context) {
if (item1Objs == null) {
int count = context.getResources().getInteger(R.integer.item1);
item1Objs = new Item1[count];
}
return item1Objs;
}
Do like this:
Resources res = getResources();
int maxSpeed = res.getInteger(R.integer.max_speed);
See here: http://developer.android.com/guide/topics/resources/more-resources.html#Integer
The reason is that any R.integer.* is a generated integer value, it's like an id which is related to your value declared in xml
You should rather call
getResources().getInteger(R.integer.*);
Here is the XML:
<string name="feature3_intro">You shot %1$d pounds of meat!</string>
Here is the java code:
int numPoundsMeat = 123;
String strMeatFormat = getResources().getString(R.string.feature3_intro);
String strMeatMsg = String.format(strMeatFormat, numPoundsMeat);
All that is appearing is: You shot %1$d pounds of meat!
help?
Thanks!
You don't need to use the standard Java String.format. Instead, your code should look like:
int numPoundsMeat = 123;
String strMeatMsg = getResources().getString(R.string.feature3_intro, numPoundsMeat);
textView.setText(strMeatMsg);
That's an overloaded version of Android's getString method that takes a varargs list and does the formatting for you.
since you haven't posted any code and i don't find anything wrong int he information you posted. If you are setting the string to a TextView, i guess you are doing
textview.setText(strMeatFormat);
instead of you should be using
textview.setText(strMeatMsg);
i have a multilingual android app, where i have put the different translations in the strings.xml in the respective directory.
now i also have a custom xml file, where i would like to reference texts like this:
<?xml version="1.0" encoding="UTF-8"?>
<rooms>
<room title="#+string/localizedtext" />
</rooms>
now when i read the title attribute in my code, i obviously get the unresolved string "#+string/localizedtext" like it is.
is it possible to somehow resolve this link to the localized text automatically?
thanks!
Almost a year later:
public static String getStringResource(Context context, String thingie) {
try {
String[] split = thingie.split("/");
String pack = split[0].replace("#", "");
String name = split[1];
int id = context.getResources().getIdentifier(name, pack, context.getPackageName());
return context.getResources().getString(id);
} catch (Exception e) {
return thingie;
}
}
That'll do it.
This might seem like a broad answer but I believe it'll clarify a lot of things for people who spent hours looking for it (I'm one of them).
The short answer is yes, you can use references in custom XML, not just for strings, but that's the example I use, for ease of understanding.
Considering the context:
res/values/strings.xml
(Default strings, usually en-US for convenience but that's up to the developer)
<resources>
<string name="sample_string">This is a sample string.</string>
</resources>
res/values-fr/strings.xml
(Localized french strings)
<resources>
<string name="sample_string">Ceci est un exemple de chaƮne</string>
</resources>
res/xml/test.xml
(Custom XML file)
<!-- #string/sample_string identifies both
the default and french localized strings,
the system settings determine which is used at runtime.
-->
<test>
<sample name="sampleName" text="#string/sample_string"/>
</test>
src/com/example/app/TestXmlParser.java
//Omitted imports for clarity.
public class testXmlParser {
public static final String ns = null;
public int parse(XmlResourceParser parser) throws XmlPullParserException,
IOException{
while(parser.next() != XmlPullParser.END_DOCUMENT){
if(parser.getEventType() == XmlPullParser.START_TAG){
if(parser.getName().equalsIgnoreCase("sample")){
// This is what matters, we're getting a
// resource identifier and returning it.
return parser.getAttributeResourceValue(ns, "text", -1);
}
}
}
return -1;
}
Use String getText(int id) to obtain the string corresponding to id (localized, if available).
Using the example above it would amount to replace :
//Return the resource id
return parser.getAttributeResourceValue(ns, "text", -1);
with :
//Return the localized string corresponding to the id.
int id = parser.getAttributeResourceValue(ns, "text", -1);
return getString(id);
The way you tried is not possible.
You might get similar functionality with <string-array> resource:
<resources>
<string-array name="room">
<item>#string/localizedText</item>
<item>#string/otherLocalizedText</item>
</string-array>
</resources>
then you would use it like this :
String[] room = getResources().getStringArray(R.array.room);
String localizedText = room[0];
String otherLocalizedText = room[1];
Localization in Android is done with resource identifiers. Check out this Android tutorial.
http://developer.android.com/resources/tutorials/localization/index.html
See discussion below.
Great answer kyis, shame I still don't have enough brownie points to rate it. To answer Nick's question, just change the last bit of code to:
int id = parser.getAttributeResourceValue(ns, "text", 0);
return (id != 0) ? getString(id) : parser.getAttributeValue(ns, "text");
Note that I used 0 for the default value of the resource as this is guaranteed never to be a real resource value. -1 would have done also.