Creating hashmap/map from XML resources

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:

  1. Have a string array of values like "BEL|Belgium", parse those string early in the program and store in a Map<>

  2. 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 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:

  1. 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).

  2. 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.

  3. 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?

  1. It allows you to use string resources and hence translate the country names for different languages.

  2. 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!