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)
Related
I'm trying to create a line chart in Android using MPAndroidChart Library and as entries I have values like 1200.10, 1300.70 and so on, but on my chart the values are rounded (1200, 1301), and I want to display the original values. How can I do that? I tried different solutions but couldn't solve the problem yet. I'm using the Kotlin language. Thanks!
for (item in reversedCashList) {
if (i <= daysNmb) {
var cashValue: String = transformDataForChart(item.value!!)
dataValsEntries.add(Entry(i, cashValue.toFloat()))
i++
}
}
Also, I'm using this formatter Class to format my values because the initial format is like 120.200,10 and I changed them to 120200.10 but this values is displayed as 120200. My Formatter Class:
private fun transformDataForChart(totalValue: String): String {
return if (totalValue.contains(".")) {
val test = totalValue.replace(".", "")
test.replace(",", ".")
} else {
totalValue.replace(",", ".")
}
}
You can try with BigDecimal, something like BigDecimal.valueOf(X).setScale(decimalPlace(usually 2), BigDecimal.ROUND_HALF_UP).floatValue()
The idea is that float cannot hold so many values as the Double, I've encountered this issue as some point as well, and I had to change everything to Double just to make it more easier to maintain... Therefor I don't think is a straight-forward method to keep everything you need in the float format.
So far I have this without crashing.
String logoImage = JO.getString("logo");
if (JO.getString("logo") == "able")
{
channel.ivLogoPic.setImageResource(R.drawable.able);
}
The problem is, It's not changing the image.
I've tried the .setDrawable and that didn't work either.
String logoImage = JO.getString("logo");
if (JO.getString("logo") == "able")
{
//int drawable1 = R.drawable.abc;
channel.ivLogoPic.setDrawable(R.drawable.able);
}
Though [ #sunil sunny ] has already mentioned a helping link, I would also like to add some solution here that could be handy.
Remember, the efficiency of this solution, in cases like yours, will decrease with increasing number of drawable resources. Meaning, do this if there are not many drawables to use.
What you can do is first get the drawables as global variables in the recyclerview adapter:
int drawable1 = R.drawable.drawable1, drawable2 = R.drawable.drawable2 ... etc.
Then inside onBindViewHolder() simply use the resources depending upon what is obtained from the JSON response.
Like:
if(logo == 1)
{
logoImageView.setDrawable(drawable1);
}
One advantage to note of this is you can change drawables easily, and not have to resort to changing names in both resource file as well as the API. Meaning, decouples the two.
PS: The codes written here are for reference only.
i figured it out in my backgroundTask.Java file I have
while (count<jsonArray.length())
{
JSONObject JO = jsonArray.getJSONObject(count);
count++;
Channel channel = new Channel(JO.getString("name"),JO.getString("logo"));
Channel logoImage = new Channel(JO.getString("logo"));
if (logoImage.equals("axle"))
{
channel.ivLogoPic.setImageResource(R.drawable.axle);
Then I had to set the change in the Adapter
if(channel.getLogo().equals("axle")){
holder.ivLogoPic.setImageResource(R.drawable.axle);
}
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'm attempting to get the int resource id for a layout resource by name, using Resources.GetIdentifier() of the Android API, but it returns 0. I'm using c#/monodroid/Xamarin, but regular java Android knowledge would apply too I suspect. Here's my code:
int resId = Resources.GetIdentifier(typeName, "layout", _activity.PackageName);
Where typeName = "FrmMain", and in my project I have the file "Resources/Layout/FrmMain.axml". Any ideas?
This is old, but for everyone getting this problem, I think it is because the resource name should be in lower case, so:
int resId = Resources.GetIdentifier("FrmMain", "layout", _activity.PackageName);
does not work, but:
int resId = Resources.GetIdentifier("frmmain", nameof(Resource.Layout).ToLower(), _activity.PackageName);
should work
I don't know why that's failing, but wouldn't something like Resource.Layout.FrmMain achieve what you're after?
edit:
According to this answer, you can (and should) use reflection to achieve what you're after, so I think you would try something like this:
var resourceId = (int)typeof(Resource.Layout).GetField(typeName).GetValue(null);
which does seem to work on my app and should get what you're after.
In my case, this issue came up when I had to upgrade the target SDK due to google's new policy since November, 2018.
I had to display some strings according to the server response code (ex : api_res_001_suc), but it did not work on the upgraded version.
The overall version, about 22 as I recall, had to be changed to 27.
The cause of the issue seems to be the default translation stuff. When I put all the default translation for every string, it worked.
My code is,
getResources().getIdentifier(resName, "string", "packageName");
I've created a ResourceHelper class to handle this situation. Here is the code:
public static class ResourceHelper
{
public static int FindId(string resourceId)
{
var type = typeof(Resource.Id);
var field = type.GetField(resourceId);
return (int)field.GetRawConstantValue();
}
public static int FindLayout(string layoutName)
{
var type = typeof(Resource.Layout);
var field = type.GetField(layoutName);
return (int)field.GetRawConstantValue();
}
public static int FindMenu(string menuName)
{
var type = typeof(Resource.Menu);
var field = type.GetField(menuName);
return (int)field.GetRawConstantValue();
}
}
Actually I'm improving it because I need to use it from another Assembly and it's restricted to work in the same Assembly of the Droid App. I'm thinking about put a generic method (or an Extension one) to do this. Here is a draft of my idea:
public static int FindResource<T>(string resourceName)
{
var type = typeof(T);
var field = type.GetField(resourceName);
return (int)field.GetRawConstantValue();
}
Hope it can help you.
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.