Rich Text with String.format

Can we do it? Yes, we can!

Posted by Grego on May 1, 2015

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:

original buy screen

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:

buy screen with updated five credit description

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:

buy screen showing html output

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

final sale screen

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