Problem
You have a string in strings.xml
with some HTML and String.format options
(%1$s
, %2$d
, etc…) that you’d like to expand. However, when you use
getString(String, Object ...)
it removes your HTML formatting.
The Problem Explained
Recently I came across a scenario where we were selling a few products on the same screen and wanted to update the pricing on this screen from a hard coded value (oops!) to one that we retrieve from Google Play. Take into consideration the following layout from this dramatic reenactment:
There’s a product title in large text. Underneath follows a product description and product pricing that contains some rich text (bold, underlines, hyperlinks, whatever). To the right of the product descriptions are the buy buttons.
The quickest and easiest way to apply the formatting to these text fields is by utilizing your application’s strings.xml and to throw in some HTML formatting. This is how our original screen was set up. Everything was done in the layout and strings xml:
<string name="five_credits_description">Gives you <b>FIVE</b> extra coins
to spend\nAvailable <b>NOW</b> for only <b>$0.99</b></string>
<string name="ten_credits_description">Gives you <b>TEN</b> extra coins
to spend\nAvailable <b>NOW</b> for only <b>$4.99</b></string>
So naturally the first the first reaction I had was to switch to using a formatted string in strings.xml:
<string name="product_description_formatted">Gives you <b>%1$s</b> extra
coins to spend\nAvailable <b>NOW</b> for only <b>%2$s</b></string>
and use getString(String, Object ...)
to substitute the arguments:
// this code is in my activity somewhere after the onCreate has
// initialized the view
TextView fiveCreditsDescriptionTextView
= (TextView)findViewById(R.id.five_credits_product_description);
if (fiveCreditsDescriptionTextView != null) {
fiveCreditsDescriptionTextView.setText(
getString(R.string.product_description_formatted,
getString(R.string.five),
this.getPrice(SKU_FIVE_CREDITS)));
}
Note: getPrice()
here is a custom local method I created for illustrative
purposes which returns the product price for a given sku. In a production
scenario you’d probably have this method in some other class.
After changing only the first description TextView, the outcome is not as expected:
So far we only changed the first description, but do you notice how the
rich text got dropped? What’s actually happening here is that the HTML is being
stripped out by getString()
. In order to get the proper rich text back, we’d
have to use getText()
instead. However, getText()
doesn’t provide the
formatted string option to expand the variables. So what do we do?
The Solution
We were most certainly on the right track with this string formatter stuff, we just needed a way to glue it all together.
The trick is to make getString()
ignore our HTML. Go back into your
strings.xml
and wrap the text in a CDATA fish (industry term) to your XML:
<string name="product_description_formatted"><![CDATA[Gives you <b>%1$s</b>
extra coins to spend\nAvailable <b>NOW</b> for only <b>%2$s</b>]]></string>
For those who forgot what a CDATA looks like here’s a quick reference with the other nonsense stripped out:
<![CDATA[ ... ]]>
This will prevent getString()
from parsing out your HTML. Then we run the
application and:
“Hey wait I thought you said this would wor—” I know, I know. I wasn’t finished
yet! In order to transform the HTML back into rich text we need to make a call
to Html.fromHtml()
on our string:
String description;
if (fiveCreditsDescriptionTextView != null) {
description = getString(R.string.product_description_formatted,
getString(R.string.five),
this.getPrice(SKU_FIVE_CREDITS));
fiveCreditsDescriptionTextView.setText(Html.fromHtml(description));
}
I also change the \n
in the strings.xml
to a <br/>
, and the final result:
Looks exactly the same as the starting point, but that’s exactly what we wanted. From a code perspective this allows us to substitute a part of the string (in this case the price) programmatically.
Clone the full sample project on github: hk0i/html-getstring-android