Android Development
TextView
setText Method
String Concatenation
Localization

Android TextView Do not concatenate text displayed with setText

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The Android Lint warning "Do not concatenate text displayed with setText" tells you to avoid building display strings by concatenating pieces in Java/Kotlin code (e.g., setText("Hello " + name)). Instead, use string resources with placeholders (getString(R.string.greeting, name)). This is critical for localization because different languages have different word orders, grammatical rules, and pluralization — a concatenated English string like "You have " + count + " items" cannot be correctly translated. String resources with format arguments let translators rearrange placeholders to fit each language's grammar.

The Problem: Concatenation in Code

java
1// BAD — triggers the Lint warning
2TextView greeting = findViewById(R.id.greeting);
3greeting.setText("Hello, " + userName + "! You have " + count + " new messages.");
4
5// Problems:
6// 1. "Hello, %s! You have %d new messages." cannot be translated as a unit
7// 2. Word order differs by language (Japanese: "新しいメッセージが5件あります")
8// 3. Pluralization varies (English: 1 message vs 2 messages; Arabic has 6 plural forms)
9// 4. Hardcoded strings are not in res/values/strings.xml

The Fix: String Resources with Placeholders

xml
1<!-- res/values/strings.xml -->
2<string name="greeting">Hello, %1$s! You have %2$d new messages.</string>
3
4<!-- res/values-ja/strings.xml (Japanese) -->
5<string name="greeting">%1$sさん、新しいメッセージが%2$d件あります。</string>
6
7<!-- res/values-de/strings.xml (German) -->
8<string name="greeting">Hallo %1$s! Du hast %2$d neue Nachrichten.</string>
java
// GOOD — uses string resource with format arguments
greeting.setText(getString(R.string.greeting, userName, count));
kotlin
// Kotlin equivalent
greeting.text = getString(R.string.greeting, userName, count)

The %1$s syntax means "first argument, as a string." %2$d means "second argument, as a decimal integer." Translators can reorder these placeholders without changing the code.

Handling Pluralization

xml
1<!-- res/values/strings.xml -->
2<plurals name="message_count">
3    <item quantity="one">You have %d new message.</item>
4    <item quantity="other">You have %d new messages.</item>
5</plurals>
6
7<!-- res/values-ar/strings.xml (Arabic — 6 plural forms) -->
8<plurals name="message_count">
9    <item quantity="zero">ليس لديك رسائل جديدة.</item>
10    <item quantity="one">لديك رسالة جديدة واحدة.</item>
11    <item quantity="two">لديك رسالتان جديدتان.</item>
12    <item quantity="few">لديك %d رسائل جديدة.</item>
13    <item quantity="many">لديك %d رسالة جديدة.</item>
14    <item quantity="other">لديك %d رسالة جديدة.</item>
15</plurals>
java
1// Use getQuantityString for plurals
2String message = getResources().getQuantityString(
3    R.plurals.message_count,
4    count,   // Used to select the plural form
5    count    // Used as the %d format argument
6);
7textView.setText(message);

Plurals cannot be handled by concatenation at all — different languages have different rules for which plural form to use based on the number.

Using Spans Instead of Concatenation

kotlin
1// BAD — concatenation for styled text
2textView.text = "Welcome, " + Html.fromHtml("<b>$name</b>") + "!"
3// Doesn't work correctly — Html.fromHtml returns Spanned, not String
4
5// GOOD — use SpannableStringBuilder
6val spannable = SpannableStringBuilder()
7    .append("Welcome, ")
8    .append(name, StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
9    .append("!")
10textView.text = spannable
kotlin
1// GOOD — use HTML in string resources
2// strings.xml: <string name="welcome">Welcome, <b>%1$s</b>!</string>
3textView.text = HtmlCompat.fromHtml(
4    getString(R.string.welcome, name),
5    HtmlCompat.FROM_HTML_MODE_LEGACY
6)

For styled text (bold, italic, colored), use string resources with HTML tags or SpannableStringBuilder. Concatenation drops formatting.

Jetpack Compose

kotlin
1// Compose uses stringResource() for the same purpose
2@Composable
3fun GreetingText(name: String, count: Int) {
4    // BAD — concatenation
5    Text("Hello, $name! You have $count messages.")
6
7    // GOOD — string resource with arguments
8    Text(stringResource(R.string.greeting, name, count))
9
10    // GOOD — plurals
11    Text(pluralStringResource(R.plurals.message_count, count, count))
12}

In Jetpack Compose, stringResource() and pluralStringResource() are the composable equivalents of getString() and getQuantityString().

Suppressing the Warning (When Appropriate)

java
1// If the text is truly not user-facing (debug, logging, etc.)
2@SuppressLint("SetTextI18n")
3private void showDebugInfo() {
4    debugText.setText("Debug: " + System.currentTimeMillis());
5}
xml
1<!-- Or suppress in lint.xml for specific cases -->
2<lint>
3    <issue id="SetTextI18n" severity="ignore" />
4</lint>

Suppress the warning only for text that will never be translated — debug views, developer tools, or internal-only screens. For anything user-facing, always use string resources.

Common Pitfalls

  • Splitting sentences across multiple string resources: getString(R.string.part1) + value + getString(R.string.part2) is just as bad as hardcoded concatenation. The sentence must be a single string resource with placeholders so translators can see and rearrange the full sentence.
  • Using String.format() without string resources: String.format("Hello %s", name) avoids concatenation but still hardcodes the template in code. Move the format string to strings.xml and use getString(R.string.key, name).
  • Forgetting positional format specifiers: %s and %d work but do not allow reordering. Use %1$s, %2$d with position numbers so translators can reorder arguments to match their language's grammar.
  • Passing the count argument only once to getQuantityString: getQuantityString(R.plurals.id, count) uses count to select the plural form but does not substitute %d in the string. You need getQuantityString(R.plurals.id, count, count) — the first count selects the form, the second fills %d.
  • Ignoring right-to-left (RTL) languages: Concatenated strings with mixed LTR/RTL text display incorrectly. String resources with placeholders handle bidirectional text properly because the Android framework applies Unicode bidirectional formatting.

Summary

  • Use string resources with %1$s, %2$d placeholders instead of concatenating strings in setText()
  • Use getQuantityString() for pluralization — different languages have different plural rules
  • Translators need to see the full sentence to rearrange word order for their language
  • Use SpannableStringBuilder or HTML in string resources for styled text
  • In Jetpack Compose, use stringResource() and pluralStringResource()
  • Only suppress the SetTextI18n warning for non-user-facing text (debug, logging)

Course illustration
Course illustration

All Rights Reserved.