Android Coden
Android 8 min lesen

Suchen in lokalen Android-Daten

Du lernst, wie du lokale Daten passend durchsuchst. So wählst du lineare Suche, binäre Suche oder Lookup bewusst.

Suchen ist eine Grundtechnik, die in Android-Apps ständig vorkommt: Du findest ein Objekt in einer Liste, prüfst einen Cache, filterst lokale Daten oder ordnest eine ID einem vorhandenen Modell zu. Für dich als Android-Entwickler ist dabei nicht nur wichtig, dass der Code korrekt ist. Du musst auch einschätzen, welche Suchstrategie zu Datenmenge, Sortierung, Zugriffsmuster und Architektur passt.

Was ist das?

Searching bedeutet, dass du in einer Datenstruktur nach einem bestimmten Wert, einem Schlüssel oder einem passenden Kriterium suchst. Das Ergebnis kann ein Element, eine Position, eine Liste von Treffern oder die klare Information sein, dass nichts gefunden wurde. In Kotlin arbeitest du dafür oft mit List, Set, Map, Sequenzen, Datenklassen und Funktionen wie firstOrNull, find, filter, contains, binarySearch oder direktem Zugriff über einen Schlüssel.

Das mentale Modell ist: Jede Suche hat Kosten. Wenn du jedes Element einzeln prüfst, nutzt du lineare Suche. Wenn deine Daten sortiert sind und du den Suchraum wiederholt halbieren kannst, nutzt du binäre Suche. Wenn du einen stabilen Schlüssel hast, etwa eine User-ID oder Produkt-ID, ist ein Lookup über Map oder Set oft passender. Diese Begriffe sind keine akademische Deko. Sie entscheiden in echten Apps darüber, ob eine Liste flüssig bleibt, ob ein Repository übersichtlich arbeitet und ob ein Offline-Cache schnell genug reagiert.

Im Android-Kontext taucht Searching häufig in der Data Layer auf. Ein Repository lädt Daten aus Netzwerk, Datenbank oder Cache und liefert sie an ViewModels weiter. In einer Compose-Oberfläche zeigst du daraus UI-State an, zum Beispiel eine Liste von Artikeln, Kontakten oder Aufgaben. Sobald du in diesem Datenbestand ein bestimmtes Objekt finden willst, triffst du eine Suchentscheidung. Bei wenigen Elementen ist eine lineare Suche meist völlig in Ordnung. Bei größeren lokalen Sammlungen, häufigen Zugriffen oder Offline-first-Apps solltest du bewusster wählen.

Wie funktioniert es?

Die lineare Suche ist die direkteste Form. Du gehst Element für Element durch eine Sammlung und prüfst, ob das aktuelle Element passt. In Kotlin sieht das oft sehr lesbar aus: items.firstOrNull { it.id == wantedId }. Für Anfänger ist das ein guter Start, weil die Absicht klar bleibt. Die Laufzeit wächst aber mit der Anzahl der Elemente. Bei zehn Einträgen merkst du nichts. Bei mehreren tausend Einträgen, die während jeder Recomposition oder bei jedem Tastendruck erneut durchsucht werden, kann daraus ein echtes Problem werden.

Die binäre Suche arbeitet anders. Sie setzt voraus, dass die Daten nach dem Suchkriterium sortiert sind. Dann prüfst du nicht jedes Element, sondern beginnst in der Mitte. Ist der gesuchte Wert kleiner, suchst du links weiter. Ist er größer, suchst du rechts weiter. So halbierst du den Suchbereich pro Schritt. Das ist sehr effizient, aber nur korrekt, wenn die Sortierung wirklich zum Vergleich passt. Eine Liste, die nach Name sortiert ist, kannst du nicht zuverlässig per Zeitstempel binär durchsuchen. Kotlin bietet dafür binarySearch, doch du musst die Vorbedingung selbst sicherstellen.

Ein Lookup über Map oder Set nutzt eine andere Idee: Du suchst nicht über eine Reihenfolge, sondern über einen Schlüssel. Eine Map<String, User> kann dir ein User-Objekt per ID liefern, ohne dass du eine Liste ablaufen musst. Ein Set<String> kann schnell beantworten, ob eine ID vorhanden ist. Das passt besonders gut für lokale Caches, Auswahlzustände, bereits geladene Elemente oder Daten, die du häufig per ID brauchst. In Android-Architekturen ist das oft sauberer, als an vielen Stellen dieselbe Liste wieder und wieder mit find zu durchsuchen.

Wichtig ist die Trennung zwischen Datenhaltung und Darstellung. In Compose solltest du teure Suche nicht unkontrolliert direkt im UI-Code ausführen, besonders nicht in Listen-Items oder häufig neu berechneten Bereichen. Besser ist, dass dein ViewModel oder Repository den passenden Zugriff vorbereitet. Die UI bekommt dann schon einen gut nutzbaren State, zum Beispiel eine Liste plus eine Map für schnelle Detailzugriffe. So bleibt der Code testbarer und die Oberfläche reagiert stabiler.

In der Praxis

Stell dir eine Offline-first-App vor, die Artikel lokal zwischenspeichert. Du bekommst aus einer Datenbank eine Liste von Artikeln und möchtest im ViewModel einzelne Artikel per ID anzeigen, etwa wenn der Nutzer aus einer Compose-Liste in eine Detailansicht wechselt. Du könntest jedes Mal linear suchen. Das ist bei kleinen Listen akzeptabel. Wenn dieselbe Suche aber häufig passiert, baust du besser einen Lookup auf.

data class Article(
    val id: String,
    val title: String,
    val updatedAt: Long
)

class ArticleCache(
    articles: List<Article>
) {
    private val articlesById: Map<String, Article> =
        articles.associateBy { it.id }

    private val articlesByUpdateTime: List<Article> =
        articles.sortedBy { it.updatedAt }

    fun findById(id: String): Article? {
        return articlesById[id]
    }

    fun findFirstUpdatedAt(timestamp: Long): Article? {
        val index = articlesByUpdateTime.binarySearchBy(timestamp) {
            it.updatedAt
        }
        return articlesByUpdateTime.getOrNull(index)
    }

    fun findTitleContaining(query: String): List<Article> {
        val normalizedQuery = query.trim().lowercase()
        if (normalizedQuery.isBlank()) return emptyList()

        return articlesById.values.filter { article ->
            article.title.lowercase().contains(normalizedQuery)
        }
    }
}

Dieses Beispiel zeigt drei unterschiedliche Sucharten. findById nutzt einen Lookup über eine Map. Das ist sinnvoll, weil IDs stabile Schlüssel sind und du wahrscheinlich oft per ID suchst. findFirstUpdatedAt nutzt binäre Suche, aber nur, weil vorher nach updatedAt sortiert wurde. findTitleContaining nutzt lineare Suche, weil Teiltextsuche über Titel ohne zusätzlichen Index jedes Element prüfen muss. Das ist nicht falsch. Es ist nur eine andere Art von Problem.

Eine praktische Entscheidungsregel lautet: Wenn du einmalig in einer kleinen, unsortierten Liste suchst, nutze eine lesbare lineare Suche. Wenn du häufig nach demselben Schlüssel suchst, baue eine Map oder ein Set. Wenn du viele Suchvorgänge über sortierbare Werte hast und die Sortierung stabil ist, kann binäre Suche passen. Wenn du komplexe Suche über große Datenmengen brauchst, etwa Volltextsuche, Filter über mehrere Felder oder Paging, gehört die Logik eher in die Datenquelle, zum Beispiel in eine Datenbankabfrage, statt in eine große Kotlin-Liste im Speicher.

Eine typische Stolperfalle ist, die Kosten einer Suche zu verstecken. firstOrNull, filter und contains sehen harmlos aus, laufen bei Listen aber meist linear. Wenn du so einen Aufruf in jedem Compose-Listenelement machst, multiplizierst du die Arbeit schnell. Beispiel: Eine Liste mit 500 Zeilen rendert, und jede Zeile sucht in einer anderen Liste nach Zusatzdaten. Das kann 500 lineare Suchen bedeuten. In einem Code-Review solltest du bei Mustern wie items.find { ... } innerhalb einer Schleife aufmerksam werden. Häufig ist eine vorher erzeugte Map die bessere Struktur.

Eine zweite Stolperfalle betrifft binäre Suche. Sie wirkt elegant, liefert aber nur sinnvolle Ergebnisse, wenn die Liste passend sortiert ist und während der Suche nicht in einer anderen Ordnung vorliegt. Auch der Vergleich muss zum Suchwert passen. Wenn du Strings ohne klare Normalisierung vergleichst, können Groß- und Kleinschreibung oder Locale-Fragen zu unerwarteten Ergebnissen führen. Für UI-Suche nach Text ist eine einfache binäre Suche meist nicht das passende Werkzeug, weil Nutzer Teilstrings, unterschiedliche Schreibweisen oder mehrere Wörter erwarten.

Für Android-Apps ist außerdem wichtig, wo die Suche stattfindet. Eine kleine Filterung im ViewModel kann passend sein, wenn die Daten schon im Speicher sind und der UI-State daraus berechnet wird. Eine Suche über einen großen lokalen Cache sollte eher in der Data Layer gekapselt werden. In Offline-first-Architekturen willst du vermeiden, dass jede UI-Stelle eigene Suchlogik erfindet. Das Repository kann entscheiden, ob es aus einer lokalen Datenbank, einem Speicher-Cache oder einer bereits vorbereiteten Map liest. Dadurch bleiben Regeln konsistent, und du kannst sie gezielt testen.

Teste Suchlogik mit klaren Randfällen. Prüfe leere Listen, nicht vorhandene IDs, doppelte Schlüssel, unterschiedliche Sortierungen und Groß- oder Kleinschreibung. Für lineare Suche erwartest du meist das erste passende Element oder eine komplette Trefferliste. Für Lookup per Map musst du wissen, was bei doppelten IDs passiert: associateBy behält bei gleichen Schlüsseln den späteren Wert. Das kann korrekt sein, sollte aber bewusst gewählt werden. Für binäre Suche brauchst du Tests, die beweisen, dass die Liste sortiert ist und der gesuchte Wert korrekt gefunden oder sauber als fehlend behandelt wird.

In der täglichen Praxis hilft dir auch der Debugger. Setze einen Breakpoint vor die Suche und prüfe, welche Datenstruktur du wirklich hast. Ist die Liste sortiert? Enthält die Map den erwarteten Schlüssel? Wird dieselbe Suche mehrfach ausgeführt? Gerade bei Compose lohnt es sich, genauer hinzusehen, ob eine Berechnung bei jeder Zustandsänderung neu passiert. Wenn ja, kannst du überlegen, ob die Suchstruktur im ViewModel vorbereitet oder mit einem geeigneten State abgeleitet werden sollte.

Fazit

Searching ist mehr als das Finden eines Elements. Du entscheidest, welche Datenstruktur und welcher Zugriff zu deinem Android-Code passen. Lineare Suche ist gut lesbar und für kleine Datenmengen oft ausreichend. Binäre Suche ist schnell, braucht aber korrekt sortierte Daten. Lookup über Map oder Set ist stark, wenn du wiederholt per Schlüssel arbeitest. Prüfe dein Verständnis aktiv: Schreibe kleine Tests für Suchfälle, beobachte mit dem Debugger die tatsächlichen Daten und achte im Code-Review darauf, ob Suchlogik an der richtigen Stelle der Architektur sitzt.

Quellen (4)
Redaktion

Geschrieben von

Redaktion

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