47

I am trying to implement a resource data structure that includes an array of arrays, specifically strings. The issue I run into is how to get the sub-array objects and their specific values. Here is what my resource file looks like....

<resources>
   <array name="array0">
     <item>
       <string-array name="array01">
         <item name="id">1</item>
         <item name="title">item one</item>
       </string-array>
     </item>
     <item>
       <string-array name="array02">
         <item name="id">2</item>
         <item name="title">item two</item>
       </string-array>
     </item>
     <item>
       <string-array name="array03">
         <item name="id">3</item>
         <item name="title">item three</item>
       </string-array>
     </item>
   </array>
</resources>

Then, in my Java code I retrieve the array and try to access the sub elements like so...

TypedArray typedArray = getResources().obtainTypedArray(R.array.array0);

TypedValue typedValue = null;

typedArray.getValue(0, typedValue);

At this point the typedArray object should represent the string-array "array01", however, I don't see how to retrieve the "id" and "title" string elements. Any help would be appreciated, thanks in advance.

JediPotPie
  • 1,068
  • 3
  • 14
  • 21
  • As pointed out by Andi Krusch, don't get caught up in the "name" attributes I used in the XML. They are just there to make my question easier to understand. (I thought) :-) – JediPotPie Dec 01 '10 at 21:10
  • wow! I thought this would be an easy one for somebody. So, is the community saying that it can't be done? – JediPotPie Dec 13 '10 at 15:17

5 Answers5

124

You can almost do what you want. You have to declare each array separately and then an array of references. Something like this:

<string-array name="array01">
    <item name="id">1</item>
    <item name="title">item one</item>
</string-array>
<!-- etc. -->
<array name="array0">
    <item>@array/array01</item>
    <!-- etc. -->
</array>

Then in your code you do something like this:

Resources res = getResources();
TypedArray ta = res.obtainTypedArray(R.array.array0);
int n = ta.length();
String[][] array = new String[n][];
for (int i = 0; i < n; ++i) {
    int id = ta.getResourceId(i, 0);
    if (id > 0) {
        array[i] = res.getStringArray(id);
    } else {
        // something wrong with the XML
    }
}
ta.recycle(); // Important!
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • i am not able to get the element of the array . can you give some solution Please – Trupti Apr 20 '14 at 17:32
  • @Trupti - Why not? If this answer isn't working for you, post a question showing what you're trying and describe what's going wrong. – Ted Hopp Apr 20 '14 at 17:35
  • i want to keep 100 question and its answer in String .xml , each question having multiple answer , some question having 5 answer and some question having 7 answerlike this .... I want to retrieve those question and answer in my java class. – Trupti Apr 20 '14 at 17:36
  • @Trupti - This solution is for an array of string arrays, not for a single string array. Also, please don't try posting your problem as a comment here; comments are not the place for that and it's not practical to post the amount of information needed to make your problem understood. The information you've provided so far tells me nothing about what your code looks like, what your xml looks like, or what might be going wrong. – Ted Hopp Apr 20 '14 at 17:41
  • Please check this link: http://stackoverflow.com/questions/23185724/retrieve-2dimensional-array-from-string-xml-in-android – Trupti Apr 20 '14 at 17:48
  • Worked for me! Thank. @TedHopp Please, do you think you could help me with this question http://stackoverflow.com/questions/25598696/recommended-way-order-to-read-data-from-a-webservice-parse-that-data-and-inse – Axel Sep 01 '14 at 23:04
  • what is the first sub array? array[0] or array[1]? – Noor Hossain Apr 23 '20 at 11:14
  • 1
    @NoorHossain - The first sub-array will be `array[0]`. – Ted Hopp Apr 23 '20 at 12:06
  • @ Ted Hopp, sir, thanks for quick reply, one question please, so what about the id> 0? (as we know that the first position of an array is 0) – Noor Hossain Apr 23 '20 at 12:51
  • @NoorHossain - The id for each subarray will always be greater than zero (provided the XML is well formed). The _index_ of the subarrays will be in the order they appear in the XML, and this order has nothing to do with the ids themselves. – Ted Hopp Apr 23 '20 at 17:09
  • @Ted Hopp, thanks sir, I understood clearly, And this is the SO for you, thanks again. – Noor Hossain Apr 23 '20 at 17:30
2

https://developer.android.com/guide/topics/resources/more-resources.html#Color

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="icons">
        <item>@drawable/home</item>
        <item>@drawable/settings</item>
        <item>@drawable/logout</item>
    </array>
    <array name="colors">
        <item>#FFFF0000</item>
        <item>#FF00FF00</item>
        <item>#FF0000FF</item>
    </array>
</resources>

This application code retrieves each array and then obtains the first entry in each array:

Resources res = getResources();
TypedArray icons = res.obtainTypedArray(R.array.icons);
Drawable drawable = icons.getDrawable(0);
TypedArray colors = res.obtainTypedArray(R.array.colors);
int color = colors.getColor(0,0);
Bill
  • 1,268
  • 14
  • 14
1

according to @Ted Hopp answer, more elegant way:

<integer-array name="id_array">
    <item>1</item>
    <item>2</item>
    <item>3</item>
</integer-array>
<string-array name="name_array">
    <item>name 1</item>
    <item>name 2</item>
    <item>name 3</item>
</string-array>
<array name="array0">
    <item>@array/id_array</item>
    <item>@array/name_array</item>
</array>

make sure sub arrays row count is identical.
enjoy write code to access array cells!
android is still a kid while maintain the ugly "item" tag of the TypedArray "array0".
in my opinion, the most flexible should be:

<array name="array0">
    <integer-array name="id_array">
        <item>1</item>
        <item>2</item>
        <item>3</item>
    </integer-array>
    <string-array name="name_array">
        <item>name 1</item>
        <item>name 2</item>
        <item>name 3</item>
    </string-array>
</array>

but don't do that because that's not android way :)

  • 1
    There's no requirement for the sub-array row counts to be identical. Android (and Java in general) supports [jagged (a.k.a. ragged) arrays](https://en.wikipedia.org/wiki/Jagged_array) with no problem. You simply need to pay proper attention to loop indexes when iterating through such arrays. – Ted Hopp Oct 10 '16 at 17:27
0

pros:

  1. auto-generated id
  2. styleable attribute
  3. access other resource reference in xml, compile error if the referred resource miss
  4. compile-time syntax check, compile error if you set wrong data type

.............................................................

  1. declare DataItem class for deserialize

DataListItem.java

class DataListItem {
    @Override
    public String toString() {
        return "DataListItem{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", drawable=" + drawable +
                ", flags=" + flags +
                ", enums=" + enums +
                '}';
    }

    public DataListItem(@IdRes int id, String name, Drawable drawable, int flags, int enums) {
        this.id = id;
        this.name = name;
        this.drawable = drawable;
        this.flags = flags;
        this.enums = enums;
    }

    public int id;
    public String name;
    public Drawable drawable;
    public int flags;
    public int enums;
}
  1. declare styleable attributes

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:app="http://schemas.android.com/apk/res-auto">
    <declare-styleable name="DataTestItem">
        <attr name="android:id" format="reference"/>
        <attr name="android:drawable" format="reference"/>
        <attr name="android:name" format="string"/>
        <attr name="name2" format="flags">
            <flag name="flag1" value="1"/>
            <flag name="flag2" value="2"/>
            <flag name="flag3" value="4"/>
        </attr>
        <attr name="name3" format="enum">
            <enum name="enum1" value="1"/>
            <enum name="enum2" value="2"/>
            <enum name="enum3" value="3"/>
        </attr>
    </declare-styleable>
</resources>
  1. create xml resource for DataItem list & map

test_list.xml

<?xml version="1.0" encoding="utf-8"?>
<list xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:name="@string/app_name"
        android:id="@+id/data1"
        android:drawable="@drawable/ic_launcher_background"
        app:name2="flag1|flag2"
        app:name3="enum1"/>
    <item
        android:name="@string/app_name"
        android:id="@+id/data2"
        android:drawable="@drawable/ic_launcher_background"
        app:name2="flag2"
        app:name3="enum2"/>
    <item
        android:name="@string/app_name"
        android:id="@+id/data3"
        android:drawable="@drawable/ic_launcher_background"
        app:name2="flag3|flag2"
        app:name3="enum3"/>
</list>

test_map.xml

<?xml version="1.0" encoding="utf-8"?>
<map xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/data1"
        android:drawable="@drawable/ic_launcher_background" />
    <item
        android:id="@+id/data2"
        android:drawable="@drawable/ic_launcher_background" />
    <item
        android:id="@+id/data3"
        android:drawable="@drawable/ic_launcher_background" />
</map>

test_set.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:name="@string/app_name" />
    <item android:name="@string/app_name2" />
    <item android:name="@string/app_name" />
    <item android:name="@string/app_name" />
</set>
  1. load DataItem list & map from xml resource

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            List<DataListItem> dataListItems = loadListFromXml(this,
                    R.xml.test_list,
                    R.styleable.DataTestItem,
                    ArrayList::new,
                    (s) -> new DataListItem(
                            s.getResourceId(R.styleable.DataTestItem_android_id, 0),
                            s.getString(R.styleable.DataTestItem_android_name),
                            s.getDrawable(R.styleable.DataTestItem_android_drawable),
                            s.getInteger(R.styleable.DataTestItem_name2, 0),
                            s.getInteger(R.styleable.DataTestItem_name3, 0)));

            Map<Integer, Drawable> dataMapItem = loadMapFromXML(this,
                    R.xml.test_map,
                    R.styleable.DataTestItem,
                    HashMap::new,
                    (s) -> s.getResourceId(R.styleable.DataTestItem_android_id, 0),
                    (s) -> s.getDrawable(R.styleable.DataTestItem_android_drawable));

            Set<String> dataSetItems = loadSetFromXml(this,
                    R.xml.test_set,
                    R.styleable.DataTestItem,
                    HashSet::new,
                    (s) -> s.getString(R.styleable.DataTestItem_android_name));


            Log.d(TAG, "loadListFromXml return " + dataListItems + ", loadMapFromXML return " + dataMapItem + ", loadSetFromXml return " + dataSetItems);

        } catch (IOException | XmlPullParserException e) {
            Log.w(TAG, "onCreate: fail to parse", e);
        }
    }

    static <T> List<T> loadListFromXml(Context context,
                                       @XmlRes int xml_resid,
                                       @StyleableRes int[] attrs,
                                       Supplier<List<T>> supplier,
                                       Function<TypedArray, ? extends T> inflater) throws IOException, XmlPullParserException {
        return collectFromXml(context,
                xml_resid,
                "list",
                "item",
                attrs,
                supplier,
                (s, ta) -> s.add(inflater.apply(ta)));
    }

    static <T> Set<T> loadSetFromXml(Context context,
                                     @XmlRes int xml_resid,
                                     @StyleableRes int[] attrs,
                                     Supplier<Set<T>> supplier,
                                     Function<TypedArray, ? extends T> inflater) throws IOException, XmlPullParserException {
        return collectFromXml(context,
                xml_resid,
                "set",
                "item",
                attrs,
                supplier,
                (s, ta) -> s.add(inflater.apply(ta)));
    }

    static <K, V> Map<K, V> loadMapFromXML(Context context,
                                           @XmlRes int xml_resid,
                                           @StyleableRes int[] attrs,
                                           Supplier<Map<K, V>> supplier,
                                           Function<TypedArray, ? extends K> keyInflater,
                                           Function<TypedArray, ? extends V> valueInflater) throws IOException, XmlPullParserException {
        return collectFromXml(context,
                xml_resid,
                "map",
                "item",
                attrs,
                supplier,
                (s, ta) -> s.put(keyInflater.apply(ta), valueInflater.apply(ta)));
    }


    static <R> R collectFromXml(Context context,
                                @XmlRes int xml_resid,
                                @Nullable String rootNodeName,
                                @Nullable String itemNodeName,
                                @StyleableRes int[] attrs,
                                Supplier<R> supplier,
                                BiConsumer<R, TypedArray> accumulator) throws IOException, XmlPullParserException {
        try (XmlResourceParser xml = context.getResources().getXml(xml_resid)) {
            final R ret = supplier.get();
            if (xml.next() == START_DOCUMENT) {
                if (xml.nextTag() == START_TAG && ((rootNodeName == null) || rootNodeName.equals(xml.getName()))) {
                    while (xml.nextTag() == START_TAG && ((itemNodeName == null) || itemNodeName.equals(xml.getName()))) {
                        final TypedArray ta = context.obtainStyledAttributes(xml, attrs);
                        accumulator.accept(ret, ta);
                        ta.recycle();
                        if (xml.nextTag() != END_TAG) {
                            break;
                        }
                    }
                }
            }
            return ret;
        }
    }
}
Yessy
  • 1,172
  • 1
  • 8
  • 13
-2

An Array isn't a name/value pair. You only can access the elements by number. The syntax in the xml is wrong. It should be that way:

<string-array name="string_array_name">
    <item>text_string1</item>
    <item>text_string2</item>
 </string-array>
Andi Krusch
  • 1,383
  • 8
  • 6
  • 1
    Strictly speaking, the syntax of the XML is not wrong. I put those names in to make it more human readable for my question. You misinterpreted my question. The question is, how do I access the elements of the sub array from the typedArray object? – JediPotPie Dec 01 '10 at 16:09