Android: get String resource at another XML namespace
Posted by Dimitri | Sep 10th, 2012 | Filed under Programming
This post shows the necessary steps to obtain the correct String resource outside the Android XML namespace. It has been written specifically for dealing with string resources associated with Preferences inflated from XML files, so this post assumes that the reader is familiar with writing customized preferences for Android. Altought, it can probably be used on other situations. This code has been tested on an emulator running Android 2.1 and also on real devices running Android 2.1, 2.2 and 4.0.4.
Imagine the following situation: you’ve written your CustomPreference class that extends from Preference and the preference layout is being inflated from a XML file. This layout file is defined by the XML:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/widget_frame" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:paddingBottom="5dp" android:paddingLeft="15dp" android:paddingRight="10dp" android:paddingTop="5dp"> <TextView android:id="@android:id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="22dp" /> <TextView android:id="@android:id/summary" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical|left" android:gravity="fill_horizontal" android:textAppearance="?android:attr/textAppearanceSmall" /> <Button android:id="@+id/bt_prefButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_marginTop="15dip" /> </LinearLayout>
So, it’s basically a Preference with a Button, nothing special. Now, at the CustomPreference class, you are setting the Button’s label by reading the buttonLabel attribute of a XML custom namespace by calling: getAttributeValue(String namespace, String name). If you create a PreferenceScreen with this CustomPreference and assign a String resource to the buttonLabel attribute, you would end up with this XML:
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom_namespace="http://your.pref.namespace.com"> <fortyonepost.com.psfaxn.CustomPreference android:defaultValue="0" android:key="customPreference1" android:summary="@string/pref_custom1_summary" android:title="@string/pref_custom1_title" custom_namespace:buttonLabel="@string/pref_custom1_button" > </fortyonepost.com.psfaxn.CustomPreference> <fortyonepost.com.psfaxn.CustomPreference android:defaultValue="0" android:key="customPreference2" android:summary="@string/pref_custom2_summary" android:title="@string/pref_custom2_title" custom_namespace:buttonLabel="@string/pref_custom2_button" > </fortyonepost.com.psfaxn.CustomPreference> <fortyonepost.com.psfaxn.CustomPreference android:defaultValue="0" android:key="customPreference3" android:summary="@string/pref_custom3_summary" android:title="@string/pref_custom3_title" custom_namespace:buttonLabel="@string/pref_custom3_button" > </fortyonepost.com.psfaxn.CustomPreference> </PreferenceScreen>
The Strings that those String resources resolve to are doesn’t matter for this example, what is important is that the XML namespace named custom_namespace is being defined at line 3. Also, take a look at lines 10, 18 and 26, they are all defining the buttonLabel attribute at the custom_namespace XML namespace but they are being assigned to a String resource at the android XML namespace. Now, when the application is executed, the labels of all buttons aren’t going to be right:
As can be observed above, all the string are not being correctly resolved to their respective String resources, because the buttonLabel attribute is outside the Android namespace. To solve this problem in a way that allows the CustomPreference class to be reused multiple times and still allow a XML file to define a the preference screen, a GetAttributeStringValue() method that resolves the String from their names at the CustomPreference class must be written, so here’s the class:
package fortyonepost.com.psfaxn; import android.content.Context; import android.content.res.Resources; import android.preference.Preference; import android.util.AttributeSet; import android.view.View; import android.widget.Button; public class CustomPreference extends Preference { //A string that holds the label that goes into the button private String buttonLabel = ""; public CustomPreference(Context context, AttributeSet attrs) { super(context, attrs); //Set the Preferences at the preferences screen from the 'custom_pref_layout.xml' file this.setLayoutResource(R.layout.custom_pref_layout); //Obtain the button label from the 'buttonLabel' attribute defined at the 'custom_namespace' XML namespace this.buttonLabel = GetAttributeStringValue(context, attrs, "http://your.pref.namespace.com", "buttonLabel", "Default Label"); } @Override public void onBindView(View view) { super.onBindView(view); //Get a reference to the Button Button bt_prefButton = (Button)view.findViewById(R.id.bt_prefButton); //Set the label obtained from the GetAttributeStringValue() method bt_prefButton.setText(this.buttonLabel); } private String GetAttributeStringValue(Context context, AttributeSet attrs, String namespace, String name, String defaultValue) { //Get a reference to the Resources Resources res = context.getResources(); //Obtain a String from the attribute String stringValue = attrs.getAttributeValue(namespace, name); //If the String is null if(stringValue == null) { //set the return String to the default value, passed as a parameter stringValue = defaultValue; } //The String isn't null, so check if it starts with '@' and contains '@string/' else if( stringValue.length() > 1 && stringValue.charAt(0) == '@' && stringValue.contains("@string/") ) { //Get the integer identifier to the String resource final int id = res.getIdentifier(context.getPackageName() + ":" + stringValue.substring(1), null, null); //Decode the string from the obtained resource ID stringValue = res.getString(id); } //Return the string value return stringValue; } }
The CustomPreference class has only a single private member being declared, a String that will later be used to store the resolved String resource from the XML file (line 13). At the constructor, the layout that this Preference relies on is being inflated (line 19).
Still inside the constructor, the private member String holds the result of the GetAttributeStringValue() method (line 21). It takes the current Context and AttributeSet as the first and second parameters. Additionally, it takes the XML namespace and the name of the attribute as well as a default parameter as the last three parameters. Notice that the XML namespace must match the one declared at the top of the preference screen XML file.
The GetAttributeStringValue() method is defined at lines 34 through 58 and the first hing it does is to obtain a handle the application’s resources (line 37). Next, a String is declared and initialized with the value of the attribute defined from the XML file (line 39). The if statement checks whether the string is null. If it is, set the stringValue to the default value passed as a parameter and the method will basically return the default string as a result (lines 41 through 45).
However, case the string isn’t null, and is composed of more than one character, starts with a ‘@’ and contains ‘@string/’ , the attribute read from the XML file is a String that points to a String resource (lines 47, 48 and 49). The String resource must now be resolved. In order to achieve that, the method getString(int id) has to be called from the application’s Resources and receive the int ID of the String defined at the R class.
This creates a problem, since we don’t know the ID of the String resource and it’s not a good idea to hard code it by calling it from the R class, because, by doing so, in this specific scenario where the preference screen is being defined by a XML file, the CustomPreference could only be used once, as the button would always display the same String.
Still, the name of the String resource is known, and there’s a handle to the application’s resources, therefore, the ID can be obtained and then passed to the Resources.getString(int id) method. This is being done at lines 52 and 54.
The getIdentifier() method takes three parameters but only the first one is being used. Basically, the String resource ID is obtained by passing the following string:
package name + “:” + resource type + “/” + resource name
And since the obtained String from the XML file is something like “@string/pref_custom1_title“, the stringValue can be used as the resource name and resource type, just by removing the ‘@’, which is done by calling substring(1) on it. It’s basically the same idea presented on this post.
After that, the method returns, as previously explained, either with the default value or the resolved String from the resources (line 57). Finally, the only thing that is left to do is to get a handle to the Button and assign the resolved String as the button’s label. Those two tasks are done inside the onBindView() method (lines 25 through 32).
That’s it! Here’s an screenshot of the app:
Final Thoughts
The goal of this example was to show how to obtain the correct String resource outside the Android XML namespace. With that, the preference screen can be defined through a XML file and it can hold multiple instances of the same CustomPreference.
On the other hand, if the preference screen is being programatically defined at Java, it’s possible to create a method at the CustomPreference class that takes the String resource ID defined at the R class as a parameter, so there’s no need to resolve it by resource name. Also, if the application only requires a single CustomPreference instance in all of its preference screens, a reference to the ID of the String resource can be hard coded by reading the ID from the R class.
The advantage of this method is that all layouts and interfaces are defined at a XML file, even the preference screen is a XML, so it’s easier to understand where a specific String resource is going to be rendered. Additionally, multiple instance of the CustomPreference class can be used on the same preference screen XML file. The biggest disadvantage is the time it takes for the preference screen to load, mainly because of the overhead introduced by the Resources.getIdentifier() and the Resources.getString() method calls.