Android Coden
Android 7 min lesen

Collection Transformations in Kotlin

Du lernst, wie map, filter und flatMap Android-Daten lesbar formen. Der Artikel zeigt Praxisregeln für Kotlin-Collections.

Collection Transformations sind ein Grundwerkzeug in Kotlin, wenn du Daten in eine Form bringen willst, die deine Android-App wirklich braucht. Statt Listen mit manuellen for-Schleifen zu durchlaufen, beschreibst du mit Funktionen wie map, filter und flatMap, was mit den Daten passieren soll. Das macht Code oft kürzer, besser testbar und leichter in ViewModels, Repositorys und Compose-Oberflächen einsetzbar.

Was ist das?

Collection Transformations sind Operationen auf Collections wie List, Set oder Map, die aus vorhandenen Daten neue Daten ableiten. Du veränderst dabei nicht unbedingt die ursprüngliche Collection, sondern erzeugst eine neue Sicht oder eine neue Liste mit angepasstem Inhalt. Das passt gut zu modernem Kotlin-Code, weil du damit Datenflüsse ausdrücken kannst, ohne jeden Zwischenschritt als veränderbare Variable zu modellieren.

Die drei wichtigsten Begriffe in diesem Roadmap-Schritt sind map, filter und flatMap. Mit map wandelst du jedes Element um. Aus einer Liste von Datenbankobjekten kann so eine Liste von UI-Modellen werden. Mit filter behältst du nur die Elemente, die eine Bedingung erfüllen. Aus allen Aufgaben werden zum Beispiel nur offene Aufgaben. Mit flatMap verarbeitest du Elemente, die selbst wieder mehrere Ergebnisse liefern, und führst diese Ergebnisse in einer flachen Liste zusammen.

Das mentale Modell ist: Du beschreibst eine Pipeline. Am Anfang stehen Rohdaten, danach folgen kleine, klar benannte Verarbeitungsschritte, und am Ende steht die Datenform, die der nächste Teil deiner App erwartet. Für Android ist das besonders wichtig, weil Daten selten direkt passend aus einer API, Datenbank oder Datei kommen. Eine REST-Antwort enthält vielleicht technische Felder, eine Room-Entity enthält Persistenzdetails, und deine Compose-UI braucht nur Text, Status und Formatierungsinformationen.

Collection Transformations ersetzen nicht jedes Kontrollkonstrukt. Eine Schleife ist weiterhin sinnvoll, wenn du komplexe Seiteneffekte, frühe Abbrüche oder sehr spezielle Performance-Anforderungen hast. In vielen Alltagsfällen ist eine Transformation aber präziser: Sie zeigt dem Leser, dass Daten umgewandelt, gefiltert oder zusammengeführt werden. Das ist ein Qualitätsgewinn, weil weniger Hilfsvariablen und weniger veränderbarer Zustand im Code auftauchen.

Wie funktioniert es?

map nimmt jedes Element einer Collection und gibt für jedes Element genau ein neues Element zurück. Die Ergebnisliste hat also dieselbe Anzahl an Einträgen wie die Eingabeliste, aber oft einen anderen Typ. In Android-Projekten nutzt du das häufig beim Mapping von Domain-Modellen zu UI-State. Aus User wird UserRowUiState, aus ArticleEntity wird Article, aus einem technischen Statuscode wird ein Text oder ein Icon-Schlüssel.

filter prüft jedes Element gegen eine Bedingung. Nur wenn die Bedingung true ist, bleibt das Element im Ergebnis. Die Ergebnisliste kann leer, gleich groß oder kleiner als die ursprüngliche Liste sein. Typische Bedingungen sind isActive, isNotBlank(), eine passende Kategorie oder ein Mindestdatum. Wichtig ist: filter verändert die Elemente nicht. Wenn du danach noch eine andere Form brauchst, kombinierst du filter mit map.

flatMap ist nützlich, wenn jedes Element mehrere Elemente erzeugen kann. Stell dir vor, du hast eine Liste von Kursen, und jeder Kurs enthält mehrere Lektionen. Wenn du alle Lektionen aller Kurse in einer einzigen Liste anzeigen willst, reicht map nicht aus, denn map würde eine Liste von Listen erzeugen. flatMap führt die inneren Listen zusammen. Das Ergebnis ist eine normale flache Liste.

Die Reihenfolge der Operationen ist ein Teil der Logik. filter vor map kann sinnvoll sein, wenn du nur relevante Elemente umwandeln willst. map vor filter kann sinnvoll sein, wenn die Bedingung erst nach der Umwandlung gut lesbar ist. Du solltest die Reihenfolge nicht nur nach Kürze wählen, sondern nach Verständlichkeit und Datenmenge. Wenn eine API-Antwort viele Einträge enthält, ist es oft günstiger, erst zu filtern und danach UI-Modelle zu bauen.

In Kotlin sind diese Operationen auf Collections standardmäßig verfügbar. Bei normalen Listen werden Zwischenergebnisse meist direkt erzeugt. Für sehr lange Transformationsketten oder große Datenmengen können Sequences interessant sein, weil sie Verarbeitungsschritte verzögert ausführen. Für diesen Roadmap-Punkt reicht aber die Grundregel: Nutze Collection-Funktionen, wenn du eine Collection klar in eine andere Collection überführen willst. Wechsle erst zu speziellen Mechanismen, wenn du ein konkretes Problem misst oder im Code klar erkennst.

Im Android-Alltag erscheinen diese Transformationen oft an Architekturgrenzen. Ein Repository liefert Domain-Daten. Ein Use Case filtert fachliche Daten. Ein ViewModel baut daraus einen UI-State. Eine Compose-Funktion erhält dann eine Liste, die bereits vorbereitet ist. Dadurch muss die UI weniger Entscheidungen treffen. Das ist ein sauberer Schnitt: Die Oberfläche rendert, während die Datenlogik vorher in testbarem Kotlin-Code liegt.

In der Praxis

Nimm eine Aufgaben-App als Beispiel. Aus einer Datenbank oder API bekommst du Aufgaben mit technischer Struktur. In deiner Compose-Liste willst du aber nur offene Aufgaben anzeigen und zusätzlich einen fertigen Anzeigetext haben. Dafür kannst du filter und map kombinieren.

data class Task(
    val id: Long,
    val title: String,
    val isDone: Boolean,
    val tags: List<String>
)

data class TaskRowUiState(
    val id: Long,
    val title: String,
    val subtitle: String
)

fun openTaskRows(tasks: List<Task>): List<TaskRowUiState> {
    return tasks
        .filter { task -> !task.isDone }
        .map { task ->
            TaskRowUiState(
                id = task.id,
                title = task.title,
                subtitle = "${task.tags.size} Tags"
            )
        }
}

Der Code sagt recht direkt, was passiert: erledigte Aufgaben werden entfernt, die übrigen Aufgaben werden in UI-Zeilen umgewandelt. Eine manuelle Schleife mit mutableListOf, if und add wäre nicht falsch, aber sie würde mehr Mechanik zeigen. Die Transformation zeigt die Absicht.

flatMap kommt ins Spiel, wenn du die Tags aller offenen Aufgaben sammeln willst. Jede Aufgabe hat eine Liste von Tags. Du möchtest daraus eine einzige Liste erzeugen, zum Beispiel für Filterchips in der UI.

fun tagsFromOpenTasks(tasks: List<Task>): List<String> {
    return tasks
        .filter { task -> !task.isDone }
        .flatMap { task -> task.tags }
        .distinct()
        .sorted()
}

Hier erzeugt flatMap aus List<Task> keine List<List<String>>, sondern direkt eine List<String>. distinct und sorted sind zusätzliche Collection-Operationen, die das Ergebnis für die UI stabiler machen. Gerade in Compose ist eine stabile und vorbereitete Liste hilfreich, weil du weniger Logik im Composable brauchst und Vorschauen leichter erstellen kannst.

Eine typische Stolperfalle ist zu viel Logik in einer einzigen Kette. Wenn eine Transformation über viele Zeilen wächst, mehrere verschachtelte Bedingungen enthält und nebenbei noch Formatierung, Fehlerbehandlung und Sortierung erledigt, wird sie schwer prüfbar. Dann solltest du Zwischenschritte benennen oder kleine Funktionen auslagern. Ein Name wie toTaskRowUiState() ist oft besser als ein langer Lambda-Ausdruck mit mehreren lokalen Sonderfällen.

Eine weitere Stolperfalle ist die Verwechslung von map und flatMap. Wenn du nach map plötzlich mit einer List<List<T>> arbeitest, war vermutlich flatMap gemeint. Das passiert häufig bei verschachtelten API-Daten, etwa Kategorien mit Artikeln, Chats mit Nachrichten oder Formularen mit Eingabefeldern. Prüfe den Ergebnistyp bewusst. Der Compiler zeigt dir den Typ, und deine IDE kann ihn oft direkt einblenden.

Du solltest außerdem auf Seiteneffekte achten. Eine Transformation sollte möglichst Daten berechnen, nicht nebenbei Logs schreiben, globale Zustände ändern oder Netzwerkaufrufe starten. Technisch kannst du in einem Lambda vieles tun, aber lesbarer Code trennt Berechnung und Seiteneffekt. Wenn du in map ein Repository aufrufst oder UI-Zustand veränderst, ist das ein Warnsignal. Für asynchrone Datenströme gibt es mit Coroutines und Flow eigene Operatoren, die ähnlich heißen können, aber einen anderen Kontext haben.

Eine praktische Entscheidungsregel lautet: Verwende map, wenn jedes Eingabeelement genau ein Ausgabeelement ergibt. Verwende filter, wenn du Elemente anhand einer Bedingung behalten oder verwerfen willst. Verwende flatMap, wenn jedes Eingabeelement null, ein oder mehrere Ausgabeelemente liefern kann und du am Ende keine verschachtelte Liste haben möchtest. Wenn du diese Regel im Code-Review erklären kannst, ist die Wahl meistens gut begründet.

Zum Üben kannst du eine kleine Liste von Testdaten anlegen und die Transformationen mit Unit-Tests prüfen. Teste dabei auch leere Listen, Listen ohne Treffer und Einträge mit leeren Tags. Setze im Debugger einen Breakpoint vor die Rückgabe und inspiziere die Zwischenergebnisse, wenn du die Kette vorübergehend in benannte Variablen aufteilst. So lernst du nicht nur die Syntax, sondern auch die Datenform nach jedem Schritt.

Fazit

Collection Transformations helfen dir, Android-Daten klar und kontrolliert von einer Form in eine andere zu bringen. Mit map, filter und flatMap beschreibst du Absicht statt Schleifenmechanik: umwandeln, auswählen, zusammenführen. Prüfe dein Verständnis aktiv, indem du vorhandene Schleifen in kleinen Kotlin-Funktionen durch Transformationen ersetzt, die Ergebnistypen bewusst liest und die Logik mit Unit-Tests oder im Code-Review erklärst.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

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