JediPotPie
JediPotPie

Reputation: 1068

Android Resource - Array of Arrays

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.

Upvotes: 47

Views: 37340

Answers (5)

Yessy
Yessy

Reputation: 1352

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;
        }
    }
}

Upvotes: 0

Bill
Bill

Reputation: 1268

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);

Upvotes: 2

Tuan Nguyen
Tuan Nguyen

Reputation: 21

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 :)

Upvotes: 1

Ted Hopp
Ted Hopp

Reputation: 234795

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!

Upvotes: 124

Andi Krusch
Andi Krusch

Reputation: 1383

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>

Upvotes: -2

Related Questions