Android Coden
Android 11 min lesen

Sortieren und Gruppieren in Kotlin

Sortieren und Gruppieren machen Listen lesbar. Du lernst, wie sortedBy und groupBy UI-Daten sauber vorbereiten.

Wenn deine App Daten aus einer Datenbank, einem Netzwerk-Call oder einer lokalen Liste bekommt, sind diese Daten selten direkt in der Form, die du anzeigen möchtest. Du musst sie oft sortieren, gruppieren oder beides kombinieren: Aufgaben nach Fälligkeit, Kontakte nach Anfangsbuchstaben, Bestellungen nach Status oder Messwerte nach Tag. In Kotlin erledigst du solche Schritte mit Funktionen wie sortedBy, sortedWith und groupBy. Für Android ist das besonders wichtig, weil eine gute Reihenfolge die Bedienung deiner App verständlicher macht und weil gruppierte Daten häufig die Grundlage für Übersichten, Statistiken und Listenabschnitte in Jetpack Compose bilden.

Was ist das?

Sortieren bedeutet, dass du Elemente in eine definierte Reihenfolge bringst. Diese Reihenfolge kann alphabetisch, numerisch, zeitlich oder fachlich sein. Wenn du eine Liste von Nachrichten nach Zeitstempel sortierst, entscheidest du zum Beispiel, ob die neuesten Nachrichten oben oder unten stehen. Wenn du Produkte nach Preis sortierst, legst du fest, ob günstige oder teure Produkte zuerst erscheinen. Das zentrale Wort ist dabei ordering: Du gibst deiner Liste eine Regel, nach der Kotlin jedes Element mit anderen Elementen vergleichen kann.

Gruppieren bedeutet, dass du Elemente nach einem gemeinsamen Merkmal in Teilmengen aufteilst. Eine Liste von Ausgaben kann nach Monat gruppiert werden, eine Liste von Aufgaben nach Status, eine Liste von Chatnachrichten nach Datum. Das Ergebnis ist nicht mehr nur eine flache List, sondern meist eine Map, bei der jeder Schlüssel eine Gruppe beschreibt und jeder Wert die Elemente dieser Gruppe enthält. In Kotlin liefert groupBy genau so ein Ergebnis: Aus List<T> wird Map<K, List<T>>, wobei K der Typ deines Gruppenschlüssels ist.

Für Android-Lernende ist das ein wichtiger Schritt auf dem Weg von reiner Syntax zu echter App-Logik. Du arbeitest nicht nur mit Variablen und Schleifen, sondern bereitest Daten so vor, dass ein Screen sie sinnvoll darstellen kann. Gerade mit Jetpack Compose willst du UI-Code möglichst klar halten. Ein Composable sollte nicht an mehreren Stellen dieselbe Liste sortieren oder Gruppenlogik verstecken. Besser ist es, Daten in einer ViewModel- oder Mapper-Schicht in eine Form zu bringen, die der Screen direkt anzeigen kann.

Der mentale Einstieg ist: Sortieren beantwortet die Frage „In welcher Reihenfolge?“, Gruppieren beantwortet die Frage „Nach welchen Körben?“. Beide Operationen verändern nicht die Bedeutung einzelner Datenobjekte, sondern ihre Struktur für Anzeige, Analyse oder Weiterverarbeitung. Das klingt klein, entscheidet aber oft darüber, ob eine App professionell wirkt. Eine ungeordnete Liste mit 80 Einträgen ist schwer nutzbar. Dieselbe Liste, sauber sortiert und in Abschnitte gegliedert, ist direkt verständlicher.

Du solltest außerdem zwischen fachlicher Sortierung und technischer Zufälligkeit unterscheiden. Daten aus einer API oder Datenbank können zwar bereits sortiert wirken, aber darauf solltest du dich nur verlassen, wenn der Vertrag das klar garantiert. Wenn deine App eine bestimmte Reihenfolge braucht, sollte diese Regel im Code sichtbar sein. Das hilft dir beim Debugging, bei Tests und später beim Code-Review.

Wie funktioniert es?

Kotlin stellt dir auf Collections viele Funktionen bereit, die keine komplizierte Infrastruktur brauchen. sortedBy ist eine der häufigsten Funktionen für eine aufsteigende Sortierung nach einem Schlüssel. Du übergibst eine Lambda-Funktion, die aus jedem Element den Sortierwert herauszieht. Bei einer Aufgabe könnte das das Fälligkeitsdatum sein, bei einem Kontakt der Name, bei einer Bestellung der Gesamtpreis.

Wichtig ist: sortedBy verändert die ursprüngliche Liste nicht. Es gibt eine neue sortierte Liste zurück. Das passt gut zu modernem Android-Code, weil unveränderliche Datenstrukturen leichter zu verstehen und zu testen sind. Wenn dein ViewModel aus einem Flow von Daten eine sortierte Liste erzeugt, bleibt der Datenfluss nachvollziehbar: Eingangsdaten kommen rein, Sortierregel wird angewendet, UI-State geht raus.

Für absteigende Sortierung nutzt du sortedByDescending. Wenn du mehrere Kriterien brauchst, nimmst du meist sortedWith zusammen mit compareBy, thenBy oder thenByDescending. Das ist im Alltag häufig nötig. Stell dir eine Aufgabenliste vor: Zuerst sollen offene Aufgaben kommen, dann nach Fälligkeit, dann alphabetisch nach Titel. Nur nach Datum zu sortieren reicht nicht, wenn erledigte Aufgaben plötzlich zwischen offenen Aufgaben stehen.

groupBy arbeitet ähnlich lesbar. Du übergibst eine Lambda-Funktion, die den Gruppenschlüssel liefert. Alle Elemente mit demselben Schlüssel landen in derselben Gruppe. Das Ergebnis ist eine Map. Die Reihenfolge der Gruppen solltest du aber bewusst behandeln. Auch wenn Kotlin bei vielen Operationen die Einfügereihenfolge praktisch erhält, ist es für lesbaren Android-Code besser, die gewünschte Reihenfolge explizit zu machen, wenn sie für die UI relevant ist. Eine Map ist fachlich zuerst eine Schlüssel-Wert-Struktur, nicht primär eine Liste von Abschnitten.

Ein typischer Datenfluss in einer Android-App sieht so aus: Repository liefert Rohdaten, ViewModel kombiniert oder transformiert diese Daten, UI-State enthält bereits sortierte oder gruppierte Modelle, Compose zeigt diese Modelle an. In Compose kann LazyColumn dann Abschnitte und Einträge rendern, ohne selbst fachliche Sortierentscheidungen zu treffen. Das trennt Zuständigkeiten: Compose beschreibt die Oberfläche, dein ViewModel oder ein Mapper bereitet die Daten vor.

Dabei solltest du Performance und Stabilität im Blick behalten. Für kleine und mittlere Listen sind sortedBy und groupBy sehr gut lesbar und völlig passend. Wenn du aber sehr große Listen bei jeder kleinen UI-Änderung neu sortierst, kann das unnötig teuer werden. Besonders in Compose ist wichtig, dass du nicht in jedem Recomposition-Durchlauf schwere Transformationen ausführst. Sortiere und gruppiere lieber dort, wo sich die Eingabedaten ändern: im ViewModel, in einem map auf einem Flow oder in einer klar benannten Hilfsfunktion. Falls du direkt in Compose ableitest, brauchst du einen guten Grund und solltest mit remember und passenden Keys arbeiten.

Null-Werte sind ein weiteres Detail. sortedBy { it.dueDate } funktioniert nur sinnvoll, wenn klar ist, wie fehlende Daten behandelt werden. Sollen Aufgaben ohne Fälligkeitsdatum unten stehen? Oder oben, weil sie Aufmerksamkeit brauchen? Kotlin kann nullable Werte sortieren, aber die fachliche Entscheidung nimmt dir die Standardfunktion nicht ab. Du solltest diese Entscheidung ausdrücken, zum Beispiel über einen Ersatzwert oder einen eigenen Comparator.

Auch bei Gruppenschlüsseln gilt: Der Schlüssel muss stabil und fachlich passend sein. Gruppierst du Chatnachrichten nach einem formatierten Datumstext, mischst du Darstellung und Logik. Besser ist oft ein fachlicher Schlüssel wie LocalDate, der später in der UI formatiert wird. So vermeidest du Fehler bei Spracheinstellungen, Zeitzonen oder späteren Designänderungen.

Ein gutes Arbeitsmodell lautet: Erst Daten fachlich formen, dann anzeigen. Sortieren und Gruppieren sind keine Dekoration, sondern Teil deiner Präsentationslogik. Sie gehören dorthin, wo du den Screen-Zustand vorbereitest. Dadurch kannst du sie mit Unit-Tests prüfen, ohne einen Emulator zu starten oder Compose-UI-Tests schreiben zu müssen.

In der Praxis

Nehmen wir eine Aufgabenliste. Du willst offene Aufgaben zuerst zeigen, innerhalb jeder Gruppe nach Fälligkeit sortieren und im Screen nach Status gruppieren. Das ist ein realistisches Beispiel: Du bereitest eine Liste für eine UI vor und kannst später aus denselben Gruppen auch kleine Zusammenfassungen ableiten, etwa die Anzahl offener Aufgaben.

import java.time.LocalDate

enum class TaskStatus {
    OPEN,
    IN_PROGRESS,
    DONE
}

data class Task(
    val id: String,
    val title: String,
    val status: TaskStatus,
    val dueDate: LocalDate?
)

data class TaskSection(
    val status: TaskStatus,
    val tasks: List<Task>
)

fun buildTaskSections(tasks: List<Task>): List<TaskSection> {
    val statusOrder = mapOf(
        TaskStatus.OPEN to 0,
        TaskStatus.IN_PROGRESS to 1,
        TaskStatus.DONE to 2
    )

    return tasks
        .sortedWith(
            compareBy<Task> { statusOrder[it.status] ?: Int.MAX_VALUE }
                .thenBy { it.dueDate ?: LocalDate.MAX }
                .thenBy { it.title.lowercase() }
        )
        .groupBy { it.status }
        .toList()
        .sortedBy { (status, _) -> statusOrder[status] ?: Int.MAX_VALUE }
        .map { (status, groupedTasks) ->
            TaskSection(
                status = status,
                tasks = groupedTasks
            )
        }
}

In diesem Beispiel passiert mehr als nur eine technische Umformung. Die Funktion buildTaskSections enthält eine fachliche Regel: offene Aufgaben sind wichtiger als laufende, erledigte Aufgaben stehen weiter unten. Aufgaben ohne Fälligkeit kommen innerhalb ihrer Statusgruppe ans Ende, weil LocalDate.MAX als Ersatzwert genutzt wird. Danach sortiert der Code nach Titel, damit Einträge mit gleichem Status und gleichem Datum stabiler und vorhersagbarer erscheinen.

Die Kombination aus sortedWith und groupBy ist absichtlich gewählt. Erst wird eine nachvollziehbare Reihenfolge für einzelne Aufgaben festgelegt, dann werden Gruppen gebildet. Anschließend werden die Gruppen selbst sortiert, damit auch die Abschnittsreihenfolge nicht zufällig aus der Eingabeliste übernommen wird. Das ist ein häufiger Punkt, den Anfänger übersehen: Eine sortierte Liste bedeutet nicht automatisch, dass gruppierte Abschnitte in jeder fachlich gewünschten Reihenfolge erscheinen. Du musst sowohl die Reihenfolge der Einträge als auch die Reihenfolge der Gruppen betrachten.

In einem ViewModel würdest du so eine Funktion typischerweise in einen UI-State einbauen. Wenn deine Daten als Flow<List<Task>> aus einem Repository kommen, kannst du mit map daraus Flow<List<TaskSection>> machen. Der Compose-Screen erhält dann bereits fertige Abschnitte und rendert nur noch. Dadurch bleibt der Screen schlank:

data class TaskListUiState(
    val sections: List<TaskSection>,
    val openCount: Int
)

fun buildTaskListUiState(tasks: List<Task>): TaskListUiState {
    val sections = buildTaskSections(tasks)

    return TaskListUiState(
        sections = sections,
        openCount = tasks.count { it.status != TaskStatus.DONE }
    )
}

Dieser zweite Schritt zeigt den Zusammenhang mit analytics-artigen Zusammenfassungen. Du gruppierst oder sortierst nicht nur für schöne Listen, sondern auch für Kennzahlen: offene Aufgaben, Umsätze pro Monat, Einträge pro Kategorie, Fehler pro Release-Version. Für kleine lokale Auswertungen in der App reicht Kotlin-Collection-Logik oft aus. Für große Datenmengen oder serverseitige Auswertungen solltest du dagegen prüfen, ob die Datenbank oder das Backend diese Arbeit übernehmen sollte.

Eine wichtige Entscheidungsregel: Sortiere und gruppiere an der Stelle, an der deine App aus Rohdaten Anzeigezustand macht. Wenn dieselbe Sortierung in drei Composables auftaucht, ist das ein Warnsignal. Dann fehlt wahrscheinlich ein gemeinsamer Mapper oder ein klarer UI-State. Du erhöhst sonst das Risiko, dass zwei Screens fast gleich aussehen sollen, aber durch leicht unterschiedliche Sortierregeln voneinander abweichen.

Eine typische Stolperfalle ist das Sortieren nach formatierten Strings. Angenommen, du formatierst ein Datum zu "02.05.2026" und sortierst dann nach diesem Text. Das kann bei anderen Formaten, Sprachen oder nicht zweistelligen Werten Probleme erzeugen. Sortiere nach LocalDate, Instant, Int, Double oder einem fachlichen Enum-Wert. Formatiere erst am Rand, also dort, wo du Text für die UI erzeugst.

Eine zweite Stolperfalle betrifft Groß- und Kleinschreibung. Wenn du Namen direkt mit sortedBy { it.name } sortierst, können Großbuchstaben anders einsortiert werden als erwartet. Für einfache Fälle kann lowercase() helfen. Für sprachlich genaue Sortierung, etwa bei Kontakten mit Umlauten oder mehreren Sprachen, kann später ein Collator relevant werden. Für deinen Roadmap-Schritt reicht zunächst: Sei dir bewusst, dass alphabetische Sortierung nicht immer nur „A bis Z“ bedeutet.

Eine dritte Stolperfalle ist unnötige Arbeit in Compose. Schreibe nicht in jedem LazyColumn-Aufruf eine lange Kette aus sortedBy, groupBy und map, wenn die Daten bereits im ViewModel vorbereitet werden können. Compose kann häufig neu zeichnen oder neu zusammensetzen. Wenn dabei jedes Mal dieselbe Liste transformiert wird, verschwendest du Laufzeit und machst Fehler schwerer zu finden. Ein klarer UI-State ist meist besser testbar.

Zum Prüfen deiner Logik brauchst du keinen vollständigen UI-Test. Ein kleiner Unit-Test reicht oft. Du kannst eine unsortierte Liste mit bewusst gemischten Statuswerten, Datumswerten und Titeln bauen, buildTaskSections aufrufen und dann prüfen, ob die Abschnitte und Einträge in der erwarteten Reihenfolge stehen. Gute Tests für Sortierung enthalten Grenzfälle: gleiche Datumswerte, fehlende Datumswerte, unterschiedliche Statuswerte und Titel mit anderer Großschreibung.

Auch im Debugger ist diese Logik gut beobachtbar. Setze einen Breakpoint nach der Sortierung und einen nach dem Gruppieren. Schau dir an, wie aus einer flachen Liste eine Map und danach eine Liste von TaskSection wird. Wenn du diesen Übergang sehen kannst, verstehst du schneller, warum eine UI-Liste falsch aussieht. Im Code-Review solltest du bei jeder neuen Liste fragen: Ist die Reihenfolge explizit? Ist der Gruppenschlüssel fachlich stabil? Wird dieselbe Logik mehrfach geschrieben? Sind Null-Werte bewusst behandelt?

Der Bezug zur Veröffentlichung einer Android-App ist ebenfalls praktisch. Vor einem Release prüfst du nicht nur, ob die App startet, sondern auch, ob zentrale Screens in realistischen Datenzuständen sauber funktionieren. Sortier- und Gruppierfehler fallen Nutzern direkt auf: neuere Einträge stehen an unerwarteter Stelle, erledigte Aufgaben mischen sich unter offene Aufgaben, Monatsgruppen erscheinen falsch. Solche Fehler sind fachlich klein, wirken aber schnell unordentlich. Deshalb gehören Testdaten mit realistischen Listen in deine Vorbereitung vor einer Veröffentlichung.

Fazit

Sortieren und Gruppieren sind grundlegende Werkzeuge, um rohe Daten in nutzbare Android-Oberflächen und kleine Auswertungen zu verwandeln. Mit sortedBy, sortedWith und groupBy kannst du Listen für Compose-Screens, ViewModels und UI-States klar vorbereiten, solange du die fachlichen Regeln bewusst ausdrückst: Reihenfolge der Elemente, Reihenfolge der Gruppen, Umgang mit Null-Werten und stabile Schlüssel. Übe das mit einer eigenen Liste aus Aufgaben, Kontakten oder Ausgaben: Schreibe eine Mapper-Funktion, prüfe sie mit einem Unit-Test, geh im Debugger Schritt für Schritt durch die Transformation und achte im Code-Review darauf, ob die Sortierregel wirklich dort steht, wo sie dauerhaft wartbar bleibt.

Quellen (4)
Redaktion

Geschrieben von

Redaktion

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