I\'m making an application where a web service fetches (amongst other) a bunch of codes from a webservice (I.e BEL, FRA, SWE). During runtime I want to translate these codes
I had a similar requirement, and I solved it in a way that is more powerful than the current solutions.
First create a resource that is an array of country arrays:
<array name="countries_array_of_arrays">
<item>@array/es</item>
<item>@array/it</item>
</array>
Then for each country add an array with the country info:
<array name="es">
<item>ES</item>
<item>ESPAÑA</item>
</array>
<array name="it">
<item>IT</item>
<item>ITALIA</item>
</array>
Some notes:
The name
values (es, it) on each country array must match the ones in countries_array_of_arrays
(you'll get an error if it doesn't).
The order of the items on each array must be the same for all the arrays. So always put the country code first, and the country name second, for example.
You can have any number of strings for each country, not just the country code and name.
To load the strings on your code do this:
TypedArray countriesArrayOfArrays = getResources().obtainTypedArray(R.array.countries_array_of_arrays);
int size = countriesArrayOfArrays.length();
List<String> countryCodes = new ArrayList<>(size);
List<String> countryNames = new ArrayList<>(size);
TypedArray countryTypedArray = null;
for (int i = 0; i < size; i++) {
int id = countriesArrayOfArrays.getResourceId(i, -1);
if (id == -1) {
throw new IllegalStateException("R.array.countries_array_of_arrays is not valid");
}
countryTypedArray = getResources().obtainTypedArray(id);
countryCodes.add(countryTypedArray.getString(0));
//noinspection ResourceType
countryNames.add(countryTypedArray.getString(1));
}
if (countryTypedArray != null) {
countryTypedArray.recycle();
}
countriesArrayOfArrays.recycle();
Here I'm loading the resources to 2 List
but you can use a Map
if you want.
Why is this more powerful?
It allows you to use string resources and hence translate the country names for different languages.
It allows you associate any type of resource to each country, not just strings. For example, you can associate a @drawable
with a country flag or an <string-array>
that contains provinces/states for the country.
Advanced example:
<array name="it">
<item>IT</item>
<item>ITALIA</item>
<item>@drawable/flag_italy</item>
<item>@array/provinces_italy</item>
</array>
Note that I haven't tried this but I've seen it in other questions. In such case you would use countryTypedArray.getDrawable(2)
to get the drawable, for example.
Simplification:
If you are only using strings for the country array, you can use an <string-array
(instead of an <array>
) like this:
<string-array name="it">
<item>IT</item>
<item>ITALIA</item>
</string-array>
And you can simplify a bit the for-loop code by directly getting an String[]
instead of a TypedArray
:
String[] countryArray = getResources().getStringArray(id);
countryCodes.add(countryArray[0]);
countryNames.add(countryArray[1]);
A note on having 2 <string-array>
:
Initially I was thinking of having 2 <string-array>
(one for the country code and another for the country name) as mentioned on another answer. However, with 2 <string-array>
you have to make sure that the number and order of the items match. With this solution you don't. It's safer in this regard. Note that, as I've said, the order of the items on each country array matters: always put the country code and country name in the same order!
It might be late for you, but for anyone else interested. If your 3 digit country codes are valid ISO 3166-1 alpha-3 codes
Locale locale = new Locale("", "SWE");
String countryName = locale.getDisplayCountry();
This gives full country name with correct language.
Also you can define a map in XML, put it in res/xml and parse to HashMap (suggested in this post). If you want to keep key order parse to LinkedHashMap. Simple implementation follows:
Map resource in res/xml
:
<?xml version="1.0" encoding="utf-8"?>
<map linked="true">
<entry key="key1">value1</entry>
<entry key="key2">value2</entry>
<entry key="key3">value3</entry>
</map>
Resource parser:
public class ResourceUtils {
public static Map<String,String> getHashMapResource(Context c, int hashMapResId) {
Map<String,String> map = null;
XmlResourceParser parser = c.getResources().getXml(hashMapResId);
String key = null, value = null;
try {
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_DOCUMENT) {
Log.d("utils","Start document");
} else if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("map")) {
boolean isLinked = parser.getAttributeBooleanValue(null, "linked", false);
map = isLinked ? new LinkedHashMap<String, String>() : new HashMap<String, String>();
} else if (parser.getName().equals("entry")) {
key = parser.getAttributeValue(null, "key");
if (null == key) {
parser.close();
return null;
}
}
} else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("entry")) {
map.put(key, value);
key = null;
value = null;
}
} else if (eventType == XmlPullParser.TEXT) {
if (null != key) {
value = parser.getText();
}
}
eventType = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return map;
}
}
Today I came across the same problem and studying developer.android.com for a long time didn't help since Android resources cannot be hashes (maps), only arrays.
So I found 2 ways:
Have a string array of values like "BEL|Belgium", parse those string early in the program and store in a Map<>
Have 2 string arrays: first with the values of "BEL", "FRA", "SWE" and second with "Belgium", "France", "Sweden".
Second is more sensitive cause you have to synchronize changes and order in both arrays simultaneously.
I've found Vladimir's answer quite compelling so I implemented his first suggestion:
public SparseArray<String> parseStringArray(int stringArrayResourceId) {
String[] stringArray = getResources().getStringArray(stringArrayResourceId);
SparseArray<String> outputArray = new SparseArray<String>(stringArray.length);
for (String entry : stringArray) {
String[] splitResult = entry.split("\\|", 2);
outputArray.put(Integer.valueOf(splitResult[0]), splitResult[1]);
}
return outputArray;
}
using it is straight forward. In your strings.xml
you have a string-array
like so:
<string-array name="my_string_array">
<item>0|Item 1</item>
<item>1|Item 4</item>
<item>2|Item 3</item>
<item>3|Item 42</item>
<item>4|Item 17</item>
</string-array>
and in your implementation:
SparseArray<String> myStringArray = parseStringArray(R.array.my_string_array);
I think the safest and cleanest approach hasn't been posted yet, so here's my $0.02:
How about creating an enum type in the code that references the string resource id for its display value:
public enum Country {
FRA(R.string.france),
BEL(R.string.belgium),
SWE(R.string.sweden),
...;
@StringRes public int stringResId;
Country(@StringRes id stringResId) {
this.stringResId = stringResId;
}
}
Then in strings.xml:
<string name="france">France</string>
...
And when you receive an item from the web service:
Country country = Country.valueOf(countryCodeFromServer);
context.getResources().getString(country.stringResId);
Build time checks will ensure that each country has a resource for each locale, which you won't have if you maintain the list as an array resource.