Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Managing Text Resources and Rich Formatting in Android

Tech 2

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
@ \\@
? \\?
< &lt;
' \\' 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, &lt;b>%1$s&lt;/b>! You possess &lt;u>%2$d&lt;/u&gt; 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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.