Android Coden
Android 8 min lesen

Strings und Templates in Kotlin

Du lernst, wie Kotlin-String-Templates Logs, Labels und Meldungen lesbar machen. Dazu gibt es Regeln für Formatierung und Sicherheit.

Strings und Templates gehören zu den kleinen Kotlin-Funktionen, die du ständig benutzt, ohne lange darüber nachzudenken. Sie helfen dir, Text aus festen Bestandteilen und variablen Werten aufzubauen: für Logs, Labels, Fehlermeldungen, Debug-Ausgaben oder kurze Statusanzeigen. In Android-Code ist das praktisch, weil viele Daten erst zur Laufzeit bekannt sind: ein Benutzername, ein Zählerstand, ein API-Status oder ein Formatierungswert aus deinem ViewModel. Wichtig ist aber, dass du Templates nicht mit sauberer UI-Lokalisierung verwechselst und sie nicht als Ablageplatz für komplizierte Logik nutzt.

Was ist das?

Ein String ist in Kotlin eine Zeichenkette, also Text. Das kann ein einzelnes Wort sein, ein Satz, ein Log-Eintrag oder ein kompletter Textblock. Ein String-Template ist eine Schreibweise, mit der du Werte direkt in diesen Text einsetzt. Statt mehrere Teile mit + aneinanderzuhängen, schreibst du den Text so, wie er später ungefähr aussehen soll, und markierst variable Stellen mit $.

Das einfache mentale Modell lautet: Ein Template ist ein Text mit Platzhaltern, die Kotlin beim Ausführen durch echte Werte ersetzt. Aus "Hallo, $name" wird zum Beispiel ein String, in dem der Inhalt der Variable name steht. Wenn du nicht nur eine Variable, sondern einen Ausdruck einsetzen willst, verwendest du geschweifte Klammern: "Anzahl: ${items.size}".

Für Android ist das relevant, weil Apps ständig Text zusammensetzen. Du zeigst Zustände an, schreibst Logs, baust Fehlermeldungen, prüfst Werte beim Debuggen und gibst Daten aus einem Repository, ViewModel oder Use Case weiter. Kotlin ist die primäre Sprache für moderne Android-Entwicklung, und String-Templates passen gut zu einem Stil, in dem Code klar, knapp und lesbar bleibt.

Trotzdem sind Templates kein Ersatz für Android-String-Ressourcen. Sobald Text in der Benutzeroberfläche erscheint und übersetzbar sein soll, gehört er normalerweise in res/values/strings.xml. Das gilt auch bei Jetpack Compose. Compose ändert die Art, wie du UI beschreibst, aber nicht die Grundregel: Nutzer sichtbarer Text soll lokalisierbar bleiben. Templates sind besonders nützlich in internem Kotlin-Code, in Logs, in Tests, in temporären Debug-Ausgaben und manchmal beim Vorbereiten von Werten, die nicht übersetzt werden müssen.

Wie funktioniert es?

Kotlin kennt zwei wichtige Formen der Interpolation. Die erste Form ist die direkte Variable mit $name. Sie ist kurz und gut lesbar, wenn du nur eine einfache Variable einsetzen willst. Die zweite Form ist ${...}. Sie ist nötig, wenn du auf Eigenschaften zugreifst, Methoden aufrufst, Rechenoperationen ausführst oder den Namen sauber vom folgenden Text trennen musst.

Ein Beispiel für den Unterschied:

val user = "Mira"
val unreadCount = 3

val greeting = "Hallo, $user"
val status = "Du hast ${unreadCount} neue Nachrichten"

Die geschweiften Klammern sind bei unreadCount hier nicht zwingend nötig, aber oft hilfreich, weil sie die Grenze des Ausdrucks sichtbar machen. Bei "${user}Profil" ist klar, dass nur user ersetzt wird und Profil normaler Text bleibt. Ohne Klammern kann der Code schnell schwerer lesbar werden oder gar nicht das bedeuten, was du erwartest.

Kotlin wertet den Ausdruck im Template aus und ruft für das Ergebnis eine Textdarstellung ab. Bei einfachen Typen wie Int, Boolean oder String ist das erwartbar. Bei eigenen Datenklassen bekommst du die Darstellung aus toString(). Eine data class erzeugt diese Darstellung automatisch, was in Logs praktisch sein kann. Du solltest aber prüfen, ob dadurch sensible Felder wie Tokens, E-Mail-Adressen oder interne IDs sichtbar werden.

Mehrzeilige Strings sind ein zweiter Baustein. Kotlin erlaubt sogenannte Raw Strings mit dreifachen Anführungszeichen. Sie behalten Zeilenumbrüche bei und eignen sich für längere Debug-Texte, Testdaten oder einfache JSON-Beispiele in Tests. Auch dort funktionieren Templates. Du kannst mit trimIndent() überflüssige Einrückung entfernen, damit der Text im Code sauber formatiert bleibt.

val endpoint = "/users"
val payload = """
    Request:
    endpoint=$endpoint
    retry=${true}
""".trimIndent()

Für Formatierung musst du unterscheiden. String-Templates setzen Werte ein, formatieren sie aber nicht automatisch nach Sprache, Währung, Datum oder Anzahl. Wenn du Dezimalstellen, Datumsformate oder lokalisierte Texte brauchst, verwendest du passende Formatierungs-APIs oder Android-String-Ressourcen mit Argumenten. Ein Template wie "Preis: $price €" sieht zwar schnell aus, kann aber für andere Sprachen, Dezimaltrennzeichen und Währungen falsch sein.

Im Alltag begegnen dir Templates besonders in drei Bereichen. Erstens in Logs: Du willst kurz sehen, welche Daten durch deinen Code laufen. Zweitens in Fehlermeldungen für Entwickler, etwa in Exceptions oder Testausgaben. Drittens in einfachen Labels oder Beschreibungen, bei denen du vorher entschieden hast, dass keine Lokalisierung nötig ist. Sobald der Text Teil der stabilen UI wird, solltest du in Richtung Ressourcen denken.

Auch Architektur spielt hinein. In einem ViewModel kannst du Rohdaten vorbereiten, aber du solltest UI-Texte nicht unnötig tief in Domain- oder Data-Schichten fest verdrahten. Ein Repository sollte eher Daten liefern als fertige deutsche Sätze bauen. Templates sind bequem, aber sie dürfen keine Schichten vermischen. Wenn dein Domain-Code plötzlich Formulierungen für Buttons oder Snackbar-Texte enthält, ist das ein Hinweis auf eine falsche Verantwortlichkeit.

In der Praxis

Stell dir vor, du baust eine kleine Compose-Ansicht, die den Ladezustand einer Nachrichtenliste zeigt. Für Logs möchtest du sehen, welcher Benutzer geladen wird und wie viele Nachrichten angekommen sind. Für die UI soll ein Text angezeigt werden. Dabei trennst du Entwicklertext von Nutzertext.

data class Message(
    val id: String,
    val title: String,
    val isRead: Boolean
)

fun logMessageState(userId: String, messages: List<Message>) {
    val unreadCount = messages.count { !it.isRead }
    println("Loaded ${messages.size} messages for user=$userId, unread=$unreadCount")
}

@Composable
fun MessageHeader(
    userName: String,
    unreadCount: Int
) {
    Text(
        text = "Hallo, $userName"
    )

    Text(
        text = "Ungelesene Nachrichten: $unreadCount"
    )
}

Dieser Code zeigt die Template-Schreibweise, aber er ist für eine echte App nur teilweise ideal. Das println wäre in Android meist durch Log.d(...) oder einen Logger ersetzt. Die Compose-Texte sind direkt im Code geschrieben und dadurch nicht gut übersetzbar. Für Lerncode ist das akzeptabel, für produktive UI solltest du String-Ressourcen verwenden.

Besser sieht der UI-Teil so aus:

<!-- res/values/strings.xml -->
<string name="message_greeting">Hallo, %1$s</string>
<string name="message_unread_count">Ungelesene Nachrichten: %1$d</string>

Und in Compose:

@Composable
fun MessageHeader(
    userName: String,
    unreadCount: Int
) {
    Text(text = stringResource(R.string.message_greeting, userName))
    Text(text = stringResource(R.string.message_unread_count, unreadCount))
}

Hier nutzt du für sichtbaren Text Androids Ressourcen-System und gibst Werte als Argumente hinein. Das ist nicht dasselbe wie ein Kotlin-String-Template, erfüllt aber denselben Zweck für UI-Texte besser: Der feste Text liegt in Ressourcen, Übersetzungen können die Reihenfolge ändern, und Zahlen oder Namen werden an definierte Stellen gesetzt. Für Logs bleibt das Kotlin-Template dagegen gut geeignet.

Eine sinnvolle Entscheidungsregel lautet: Nutze String-Templates für Entwicklertext und kurzlebigen technischen Text; nutze String-Ressourcen für Nutzertext. Wenn ein Text in Screenshots, QA, Übersetzungen, Accessibility-Prüfungen oder Release-Reviews auftaucht, sollte er nicht als festes Template im Kotlin-Code versteckt sein.

Eine typische Stolperfalle sind zu komplexe Ausdrücke im Template. So ein Log ist schwer zu lesen:

println("Result=${users.filter { it.isActive }.map { it.name }.sorted().joinToString()}")

Der Code funktioniert, aber du musst beim Lesen mehrere Dinge gleichzeitig verstehen: Filtern, Mapping, Sortierung und Textausgabe. Besser ist es, Zwischenschritte zu benennen:

val activeUserNames = users
    .filter { it.isActive }
    .map { it.name }
    .sorted()

println("Active users: ${activeUserNames.joinToString()}")

Das ist länger, aber klarer. Der Name activeUserNames erklärt die Absicht. Im Debugger kannst du den Zwischenwert ansehen. In einem Code-Review erkennt man schneller, ob die Logik korrekt ist.

Eine zweite Stolperfalle betrifft sensible Daten. Templates machen es sehr leicht, Objekte vollständig auszugeben:

println("Login response: $response")

Wenn response ein Token, eine Session-ID oder personenbezogene Daten enthält, landet dieser Inhalt möglicherweise in Logs. In Android-Projekten können Logs bei Debug-Builds hilfreich sein, aber sie sollten trotzdem bewusst geschrieben werden. Gib lieber gezielt harmlose Felder aus:

println("Login response: success=${response.isSuccess}, userId=${response.userId}")

Auch hier gilt: Prüfe, was toString() deiner Objekte preisgibt. Bei data class ist die Ausgabe bequem, aber nicht immer passend.

Eine dritte Stolperfalle ist Formatierung. Wenn du etwa einen Fortschritt oder Betrag ausgibst, ist ein Template nicht automatisch nutzerfreundlich:

val progress = 0.756
val label = "Fortschritt: ${progress * 100}%"

Das kann 75.6% ergeben, vielleicht aber möchtest du 76 %, eine feste Anzahl Dezimalstellen oder eine lokalisierte Schreibweise. Für Logs ist das oft ausreichend. Für UI brauchst du bewusste Formatierung. Bei Prozenten, Datum, Zeit, Geld und Mehrzahlformen solltest du nicht nur interpolieren, sondern passende Formatierer oder Ressourcen nutzen. Besonders Mehrzahlformen sind in Android ein eigenes Thema, weil Sprachen unterschiedliche Regeln haben.

In Tests sind Templates ebenfalls nützlich. Du kannst Assertion-Nachrichten bauen, die dir bei einem Fehlschlag direkt den relevanten Kontext zeigen:

check(actual == expected) {
    "Expected message count=$expected, but was=$actual for user=$userId"
}

Der Vorteil ist nicht nur kürzerer Code. Eine gute Fehlermeldung spart dir Zeit, weil sie direkt sagt, welcher Wert falsch war und in welchem Kontext der Fehler auftrat. Das ist ein Qualitätsaspekt: Saubere Templates können Debugging und Testanalyse erleichtern.

Achte trotzdem darauf, Templates nicht als Ersatz für klare Typen zu verwenden. Wenn du Daten nur noch als zusammengesetzte Strings durch deine App reichst, verlierst du Struktur. Ein UserDisplayState mit Feldern wie name, unreadCount und isLoading ist besser testbar als ein einziger fertiger Satz. Baue Text möglichst spät, dort wo er gebraucht wird: im UI-Layer, im Logger oder in einer klaren Formatierungsfunktion.

Fazit

String-Templates sind ein einfaches, aber wichtiges Werkzeug in Kotlin: Du setzt Werte lesbar in Text ein und vermeidest unübersichtliche Verkettungen. Für Android solltest du sie bewusst einsetzen: gut für Logs, Tests und technische Meldungen, vorsichtig bei UI-Texten, Formatierung und sensiblen Daten. Übe das gezielt, indem du in einem kleinen Compose-Screen direkte Textverkettungen durch Templates ersetzt, danach UI-Texte in String-Ressourcen verschiebst und im Debugger prüfst, welche Werte wirklich ausgegeben werden. In einem Code-Review kannst du zusätzlich nach drei Fragen suchen: Ist der Text lokalisierbar, falls Nutzer ihn sehen? Ist das Template lesbar oder steckt zu viel Logik darin? Gibt der String nur Daten aus, die auch wirklich in Logs stehen dürfen?

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.