I'd like to know a better approach to improve performance of my program. The objective is to load resources automatically, I'm using names of string or string-array elements. For example, if I have the next resources:
<string name="temperature">temperature</string>
<string name="pressure">pressure</string>
<string name="velocity">velocity</string>
...
<string-array name="measures">
<item>#string/temperature</item>
<item>#string/pressure</item>
<item>#string/velocity</item>
...
</string-array>
<string name="name_temperature">Temperature</string>
<string name="name_pressure">Pressure</string>
<string name="name_velocity">Velocity</string>
...
<string-array
name="name_measures">
<item>#string/name_temperature</item>
<item>#string/name_pressure</item>
<item>#string/name_velocity</item>
...
</string-array>
<string-array name="units_temperature">
<item>K</item>
<item>°C</item>
<item>°F</item>
<item>R</item>
</string-array>
I'm loading resources this way:
measuresMap = new HashMap<String, String>();
String[] measures = getResources().getStringArray(R.array.measures);
for(int i = 0; i < measures.length; i++){
measuresMap.put(measures[i], getResources().getString(getResources().getIdentifier("name_" + measures[i], "string", getActivity().getPackageName())).toString());
}
i.e. I'm mapping the string-array values from 'measures' to it's corresponding string 'name_<>'.
I'm using a Spinner to select the measure, for example, 'Temperature':
measureSpinner = (Spinner) view.findViewById( R.id.spinnerConverter );
setSpinner(measureSpinner, R.array.name_measures, android.R.layout.simple_spinner_item, android.R.layout.simple_spinner_dropdown_item);
When an item is selected, a method retrieves the key from the Map depending on the item's string of the Spinner (getKeyByValueFromMap from here):
String[] units = getResources().getStringArray(getResources().getIdentifier("units_" + getKeyByValueFromMap(measuresMap, measureSpinner.getSelectedItem().toString()), "array", getActivity().getPackageName()));
public <T, E> T getKeyByValueFromMap(Map<T, E> map, E value) {
for (Map.Entry<T, E> entry : map.entrySet()) {
if (value.equals(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
I need to do this to populate a NumberPicker:
String[] units = getResources().getStringArray(getResources().getIdentifier("units_" + getKeyByValueFromMap(measuresMap, measureSpinner.getSelectedItem().toString()), "array", getActivity().getPackageName()));
I think this is somehow inefficient. I read something about loading arrays with a TypedArray. I thought of a multidimensional String array. The objective is the same: load resources automatically (driven by the for loop to populate the Map). Is a HashMap the best option? It would be easier if a resource name could be defined with another resource string:
<string name="#string/temp">Temperature</string>
Every time I read a question about performance, a bell rings in my mind asking if there is really a performance issue. If you work with a small quantity of values, you won't really notice any bad performance. And if you work with lots of data, you should probably use sqlite instead.
If you won't be using #string/name_temperature per se, it can go directly on the array and make it similar to the example on the documentation
And yes, you can make use of TypedArray:
TypedArray measures = context.getResources().obtainTypedArray(R.array.name_measures);
It understands length() and getString(index).
Back to your code, I don't really understand your need of a map here, unless you are really worried of putting the strings directly in your arrays instead of the IDs.
Also, I see you use the name to generate the id of the Spinner; in this context, it doesn't help the performance and, more important, it does not make the code clearer either.
So the real answer:
I would take the references to the Spinners somewhere accessible. It might be nice to reify the need of a different key, and make a sublclass of Spinner that can convert indexed positions to the strings I want. In other words, delegate to the Spinner the responsibility of storing and converting positions to strings.
Since here I do need to map positions to the strings (instead of just the IDs), I could use a simple String[] and then, onItemSelected just access the position, geting the desired String, or setting it as selected (then when you ask your Spinner which value it has, you can just ask for its selected value, remember you now have it's reference on some attribute).
Related
For example, i have my_string.xml file with strings:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="my_string">My string</string>
<string name="another_string">Another string</string>
{...}
</resources>
Is it possible to get this strings to list/hashmap programmatically? Yeh, i know that i can take it by name or something like this. But i need to get it dynamically programmatically, for example, to HashMap<String, String>
Or all string resources will merge together after building and it's not possible to separate it?
If you want to access all the Strings from the strings.xml file you could use reflection on the R.string class.
Field[] fields = R.strings.class.getFields();
String[] allStringsNames = new String[fields.length];
for (int i =0; i < fields.length; i++) {
allStringsNames[i] = fields[i].getName();
}
You can then store them in Hashmap or wherever you want
Put the file in the raw folder. Then you have to open the file
InputStream in = getResources().openRawResource(R.raw.yourfile);
and parse the content manually, for instance save the list as xml or json and parse it and build your desired HashMap or whatever you like. Keep in mind that this may be a blocking operation and should not run on the main UI thread, run it async, but it depends on the length of the file you try to parse, but in general you should run that in an async thread
---- Update
You could do something like this:
int stringRes[] = {R.string.my_string, R.string.another_string}
List<String> myStrings = new ArrayList<String>();
for (int id : stringRes){
String str = getResources().getString(id);
// TODO do some if check if you want to keep that string or whatever you want to ...
myStrings.add(str);
}
you could store them into a HashMap(but getResources().getString() acts already like a HashMap ) or List
The whole point of the XML file is to act as a "hashmap" kind of... you can always get a string by using context and the R.string reference. I'm assuming you are aware of this, so then you must be trying to create a simplified reference to this?
Creating a HashMap requires either static references (which are not available for XML) or a runtime creation of the list. You can create the list in the Application class, but I would caution against that. You may have trouble referencing the context but again, you can use the Application constructor to make sure you have a reference to context.
Beyond that, you should check into the StringArray resource. It does not allow you to reference a string using another string because it is an array. Here are the docs:
http://developer.android.com/guide/topics/resources/string-resource.html#StringArray
If it's super-important to use a HashMap then you should probably not use XML and should just create a static class. The XML resource files are primarily used for centralized data (to ease modifications to static strings) and, more so, multi-lingual purposes. Static HashMaps are not the proper use-case.
i want to get a string from strings.xml. i know how to do this. but my problem is something else:
i have a String Variable which changes every time, and every time it changes, i want to look at strings.xml and check if that String variable exists in Strings.xml, then get text.
for example:
String title="sample title" \\ which changes
String city= "sample city"
String s = getResources().getString(R.string.title);
in the third line: title is a String, and there isn't any "title" named String in Strings.xml
how can i do this? please help me
As far as I can tell, you could use public int getIdentifier (String name, String defType, String defPackage). Its use is discouraged, though.
To use it (I haven't done it but I had once read about the method) you probably would need to:
int identifier = getResources().getIdentifier ("title","string","your.package.name.here");
if (identifier!=0){
s=getResources().getString(identifier);
}
else{
s="";//or null or whatever
}
One thing you could do to get strings from dynamic keys is make 2 string arrays and put them in a HashMap.
arrays.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="title_keys">
<item>title1</item>
<item>title2</item>
<item>title3</item>
</string-array>
<string-array name="title_values">
<item>Real Title 1</item>
<item>Real Title 2</item>
<item>Real Title 3</item>
</string-array>
</resources>
And in your code:
String[] titleKeys = getResources().getStringArray(R.array.title_keys);
String[] titleValues = getResources().getStringArray(R.array.title_values);
HashMap<String, String> titles = new HashMap<String, String>();
for(int i = 0; i < titleKeys.length; i++) {
titles.put(titleKeys[i], titleValues[i]);
}
Finally, to get your titles from a dynamic key:
titles.get(titleFromSomewhere);
You wouldn't. You use strings.xml for constant strings. What you want to do is one of two things.
1)You want a String to be constant, but one of a few options (for example, one item in a list of countries). In this case, put all of the options in strings.xml, and hold which one you're currently using in an int. When you need to get the actual string, use getString().
2)It really can be any string (for example, a user entered name). In that case it doesn't go in strings.xml at all, you just use a String variable.
This can not be done, resources are converted to unique ints in R.java, and those are used to look up your actual string resources.
So R.string.title is actually something like 0x78E84A34.
You can write your own class which manages strings for you utilizing a HashMap<String,String> to lookup full strings for shorter "key" strings.
How to search for a specific text inside a string-array item element? The following is an example of the xml file. The string-array name is android. I have some items inside the string-array. Now I want to do a search for the word "software". Please tell me how to do that?
<?xml version="1.0" encoding="utf-8"?><resources>
<string-array name="android">
<item>Android is a software stack for mobile devices that includes an operating system, middleware and key applications.</item>
<item>Google Inc. purchased the initial developer of the software, Android Inc., in 2005..</item>
</string-array>
This method has better performances:
String[] androidStrings = getResources().getStringArray(R.array.android);
if (Arrays.asList(androidStrings).contains("software") {
// found a match to "software"
}
Arrays.asList().contains() is faster than using a for loop.
I assume that you want to do this in code. There's nothing in the api to do text matching on an entire String array; you need to do it one element at a time:
String[] androidStrings = getResources().getStringArray(R.array.android);
for (String s : androidStrings) {
int i = s.indexOf("software");
if (i >= 0) {
// found a match to "software" at offset i
}
}
Of course, you could use a Matcher and Pattern, or you could iterate through the array with an index if you wanted to know the position in the array of a match. But this is the general approach.
I have preferences where you can enable/disable what items will show up on the menu. There are 17 items. I made a string array in values/arrays.xml with titles for each of these 17 items.
I have preferences.xml which has the layout for my preferences file, and I would like to reference a single item from the string array to use as the title.
How can I do this?
In the Android developer reference, I see how I can reference a single string with XML, but not how I can reference a string from an array resource in XML.
In short: I don't think you can, but there seems to be a workaround:.
If you take a look into the Android Resource here:
http://developer.android.com/guide/topics/resources/string-resource.html
You see than under the array section (string array, at least), the "RESOURCE REFERENCE" (as you get from an XML) does not specify a way to address the individual items. You can even try in your XML to use "#array/yourarrayhere". I know that in design time you will get the first item. But that is of no practical use if you want to use, let's say... the second, of course.
HOWEVER, there is a trick you can do. See here:
Referencing an XML string in an XML Array (Android)
You can "cheat" (not really) the array definition by addressing independent strings INSIDE the definition of the array. For example, in your strings.xml:
<string name="earth">Earth</string>
<string name="moon">Moon</string>
<string-array name="system">
<item>#string/earth</item>
<item>#string/moon</item>
</string-array>
By using this, you can use "#string/earth" and "#string/moon" normally in your "android:text" and "android:title" XML fields, and yet you won't lose the ability to use the array definition for whatever purposes you intended in the first place.
Seems to work here on my Eclipse. Why don't you try and tell us if it works? :-)
Maybe this would help:
String[] some_array = getResources().getStringArray(R.array.your_string_array)
So you get the array-list as a String[] and then choose any i, some_array[i].
The better option would be to just use the resource returned array as an array,
meaning:
getResources().getStringArray(R.array.your_array)[position]
This is a shortcut approach of other mentioned approaches but does the work in the fashion you want. Otherwise Android doesn't provide direct XML indexing for XML based arrays.
Unfortunately:
It seems you can not reference a single item from an array in values/arrays.xml with XML. Of course you can in Java, but not XML. There's no information on doing so in the Android developer reference, and I could not find any anywhere else.
It seems you can't use an array as a key in the preferences layout. Each key has to be a single value with it's own key name.
What I want to accomplish:
I want to be able to loop through the 17 preferences, check if the item is checked, and if it is, load the string from the string array for that preference name.
Here's the code I was hoping would complete this task:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
ArrayAdapter<String> itemsArrayList = new ArrayAdapter<String>(getBaseContext(), android.R.layout.simple_list_item_1);
String[] itemNames = getResources().getStringArray(R.array.itemNames_array);
for (int i = 0; i < 16; i++) {
if (prefs.getBoolean("itemKey[i]", true)) {
itemsArrayList.add(itemNames[i]);
}
}
What I did:
I set a single string for each of the items, and referenced the single strings in the . I use the single string reference for the preferences layout checkbox titles, and the array for my loop.
To loop through the preferences, I just named the keys like key1, key2, key3, etc. Since you reference a key with a string, you have the option to "build" the key name at runtime.
Here's the new code:
for (int i = 0; i < 16; i++) {
if (prefs.getBoolean("itemKey" + String.valueOf(i), true)) {
itemsArrayList.add(itemNames[i]);
}
}
Another way of doing it is defining a resources array in strings.xml like below.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE resources [
<!ENTITY supportDefaultSelection "Choose your issue">
<!ENTITY issueOption1 "Support">
<!ENTITY issueOption2 "Feedback">
<!ENTITY issueOption3 "Help">
]>
and then defining a string array using the above resources
<string-array name="support_issues_array">
<item>&supportDefaultSelection;</item>
<item>&issueOption1;</item>
<item>&issueOption2;</item>
<item>&issueOption3;</item>
</string-array>
You could refer the same string into other xmls too keeping DRY intact.
The advantage I see is, with a single value change it would effect all the references in the code.
The answer is quite easy to implement.
String[] arrayName = getResources().getStringArray(R.array.your_string_array);
and now you can access any element of the array by index (let suppose i'th index), then you can access it by arrayName[i]
I hope you understand this
I have a ListView showing names of countries.
I have stored the names in strings.xml as a string-array called country_names.
In populating the ListView, I use an ArrayAdapter which reads from strings.xml:
String[] countryNames = getResources().getStringArray(R.array.country_names);
ArrayAdapter<String> countryAdapter = new ArrayAdapter<String>(this, R.layout.checked_list, countryNames);
myList.setAdapter(countryAdapter);
Now I also have a CountryCode for each country. When a particular country name is clicked on the ListView, I need to Toast the corresponding CountryCode.
I understand implementing a HashMap is the best technique for this. As far as I know, the HashMap is populated using put() function.
myMap.put("Country",28);
Now my questions are:
Is it possible to read the string.xml array and use it to populate the Map? I mean, I want to add items to the Map, but I must be able to do so by reading the items from another array. How can I do this?
The basic reason I ask is because I want to keep the country names and codes in a place where it is easier to add/remove/modify them.
The string-arrays are stored in strings.xml. Where must similar integer arrays be stored? In values folder, but under any specific XML file?
As one of the possibilities, you may store 2 different arrays in XML: string array and integer array, and then programmatically put them in the HashMap.
Definition of arrays:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="countries_names">
<item>USA</item>
<item>Russia</item>
</string-array>
<integer-array name="countries_codes">
<item>1</item>
<item>7</item>
</integer-array>
</resources>
And code:
String[] countriesNames = getResources().getStringArray(R.array.countries_names);
int[] countriesCodes = getResources().getIntArray(R.array.countries_codes);
HashMap<String, Integer> myMap = new HashMap<String, Integer>();
for (int i = 0; i < countriesNames.length; i++) {
myMap.put(countriesNames[i], countriesCodes[i]);
}
It may be a file with any name. See this