Managing Text Resources and Rich Formatting in Android
Android's framework decouples interface copy from business logic by centralizing textual definitions in XML files located under res/values/. This architecture streamlines localization workflows and guarantees consistent rendering across the application.
Core String Definitions
Single text entries use the <string> element. The name attribute generates the compile-time resource ID. Files require <resources> as the root container.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="auth_prompt">Enter your credentials below</string>
</resources>
Access the compiled value through the context resource manager:
Kotlin
val promptText: String = resources.getString(R.string.auth_prompt)
Java
String loginHint = getString(R.string.auth_prompt);
If the XML contains embedded styling (spans), retrieve it using getText() instead. This method returns a CharSequence that preserves typographical attributes.
String Arrays
Ordered collections of text utilize the <string-array> container, with individual values wrapped in <item> tags.
<resources>
<string-array name="nav_tabs">
<item>Dashboard</item>
<item>Analytics</item>
<item>Configuration</item>
</string-array>
</resources>
Retrieve the compiled array directly:
val menuOptions: Array<String> = resources.getStringArray(R.array.nav_tabs)
Quantity Strings (Plurals)
Grammatical pluralization rules differ across languages. Android handles these variations using <plurals> blocks with specific quantity attributes: zero, one, two, few, many, and other. The platform selects the correct string based on linguistic grammar, not purely mathematical value.
Implementation Note: Always define at least one and other variants. Translators map these base quantities to language-specific rules. For instance, Slavic languages often require three distinct forms, whereas CJK languages typically only utilize other.
<plurals name="search_count">
<item quantity="one">Located %d record.</item>
<item quantity="other">Located %d records.</item>
</plurals>
To resolve the correct plural and inject the number simultaneously, pass the count parameter twice:
val resultTotal = 42
val formattedResult = resources.getQuantityString(
R.plurals.search_count,
resultTotal,
resultTotal
)
The second argument selects the plural rule, while the third substitutes the %d specifier. Omit the final argument if the string contains no format placeholders.
Escaping and Dynamic Formatting
XML parsers interpret specific symbols as markup. Use escape sequences or double-quote wrappers to render literal characters.
| Character | Escaped Form |
|---|---|
@ |
\\@ |
? |
\\? |
< |
< |
' |
\\' or enclose in "..." |
" |
\\" |
Ensert dynamic data using positional arguments like %1$s (String) and %2$d (Integer):
<string name="task_log">%1$s finalized %2$d operations at %3$tH:%3$tM</string>
val logEntry = getString(
R.string.task_log,
"Operator_7",
15,
Calendar.getInstance()
)
Retaining HTML Styling with Format Strings
Standard formatting functions strip HTML markup. To preserve visual styling while substituting variables, escape angle brackets in the resource and reconstruct spans after fomratting.
<string name="welcome_msg">Greetings, <b>%1$s</b>! You possess <u>%2$d</u> alerts.</string>
val baseContent = getString(R.string.welcome_msg, "Admin", 3)
val styledOutput = Html.fromHtml(baseContent, Html.FROM_HTML_MODE_LEGACY)
messageView.text = styledOutput
Always sanitize dynamic inputs using TextUtils.htmlEncode() before injection to prevent markup corruption or injection vulnerabilities.
Programmatic Span Construction
SpannableStringBuilder enables precise control over typography and color without XML tags. The following utility demonstrates a refactored approach for concatenating text while applying distinct spans to each segment:
Kotlin
fun assembleStyledSegments(vararg parts: Pair<CharSequence, Any>): CharSequence {
val builder = SpannableStringBuilder()
parts.forEach { (textChunk, spanDefinition) ->
val chunkStart = builder.length
builder.append(textChunk)
builder.setSpan(
spanDefinition,
chunkStart,
builder.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
return builder
}
val combinedText = assembleStyledSegments(
"Status: " to StyleSpan(Typeface.BOLD),
"Active" to ForegroundColorSpan(Color.GREEN)
)
Java
public static CharSequence mergeStyledSegments(CharSequence... elements) {
SpannableStringBuilder result = new SpannableStringBuilder();
int cursor = 0;
while (cursor < elements.length) {
CharSequence chunk = elements[cursor];
Object styleObj = elements[cursor + 1];
int start = result.length();
result.append(chunk);
result.setSpan(styleObj, start, result.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
cursor += 2;
}
return result;
}
CharSequence display = mergeStyledSegments(
"Note: ", new StyleSpan(Typeface.ITALIC),
"Review Required", new ForegroundColorSpan(Color.RED)
);
Metadata-Driven Styling via Annotations
For complex internationalization requirements, the <annotation> tag embeds structural metadata into resources. The compiler converts these tags into Annotation spans, allowing runtime style resolution based on key-value pairs.
<!-- res/values/strings.xml -->
<string name="badge_text">Switch to <annotation key="typography">premium_tier</annotation> plans</string>
<!-- res/values-fr/strings.xml -->
<string name="badge_text">Passer aux forfaits <annotation key="typography">premium_tier</annotation></string>
Extract the annotations and apply custom typography at runtime:
val originalText = getText(R.string.badge_text) as SpannedString
val spanCollection = originalText.getSpans(0, originalText.length, Annotation::class.java)
val editableText = SpannableString(originalText)
spanCollection.forEach { tag ->
if (tag.key == "typography" && tag.value == "premium_tier") {
val targetFont = ResourcesCompat.getFont(context, R.font.inter_bold)
editableText.setSpan(
TypefaceSpan(targetFont),
originalText.getSpanStart(tag),
originalText.getSpanEnd(tag),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
targetView.text = editableText
Becuase Annotation implements ParcelableSpan, styled text survives Intent extras and Bundle serialization without losing structural data, provided the destination component processes the spans identically.