Data Reliability Review
Prüfe, ob App-Daten auch bei Fehlern konsistent, aktuell und nachvollziehbar bleiben.
Eine Data Reliability Review ist eine gezielte Prüfung deiner Datenlogik: Du bewertest, ob deine App unter schlechten Bedingungen weiterhin nachvollziehbare, konsistente und ausreichend aktuelle Daten zeigt. Dabei geht es nicht um hübsche UI-Zustände, sondern um Vertrauen. Wenn das Netzwerk ausfällt, ein Request fehlschlägt, ein lokaler Cache veraltet ist oder mehrere Datenquellen widersprüchliche Informationen liefern, muss deine App klare Regeln haben. Genau diese Regeln prüfst du in einer Data Reliability Review.
Was ist das?
Eine Data Reliability Review ist ein strukturierter Blick auf das Verhalten deiner Datenebene unter Fehlern. Du fragst nicht nur: “Funktioniert der normale Ladefall?”, sondern: “Was passiert, wenn etwas schiefgeht?” In modernen Android-Apps betrifft das vor allem Repositories, lokale Datenquellen wie Room, Remote-Datenquellen über Retrofit oder Ktor, Kotlin Coroutines, Flow und die Zustände, die am Ende in Jetpack Compose sichtbar werden.
Der Kern besteht aus drei Begriffen: consistency, freshness und errors. Konsistenz beschreibt, ob verschiedene Teile deiner App dasselbe Datenbild haben oder ob klar geregelt ist, warum sie kurzzeitig abweichen dürfen. Freshness beschreibt, wie aktuell Daten sein müssen und woran deine App erkennt, dass Daten veraltet sind. Fehler beschreiben nicht nur Exceptions, sondern alle Situationen, in denen die App keine sichere Aussage über Daten treffen kann.
Für Einsteiger ist das wichtigste mentale Modell: Daten sind kein einzelner Wert, sondern ein Verlauf. Ein Bildschirm zeigt nicht einfach “die Daten”, sondern einen Zustand aus lokalem Speicher, Netzwerkantworten, Synchronisierung, Zeit und möglichen Fehlern. Eine gute Datenarchitektur macht diesen Verlauf sichtbar und testbar. Eine schlechte Datenarchitektur versteckt ihn in zufälligen try/catch-Blöcken, UI-Sonderfällen und stillen Annahmen.
Im Android-Kontext passt die Review direkt zur Data Layer. Die offizielle Architektur empfiehlt, Datenzugriff über Repositories zu kapseln und Datenquellen klar zu trennen. Dadurch kannst du prüfen, ob ein Repository bei Fehlern einen stabilen Vertrag erfüllt: Gibt es zuerst gecachte Daten? Wird ein Netzwerkfehler als eigener Zustand gemeldet? Wird ein veralteter Cache markiert? Werden Schreiboperationen erneut versucht oder sauber abgelehnt?
Wichtig ist: Reliability bedeutet nicht, dass nie Fehler auftreten. Das wäre unrealistisch. Reliability bedeutet, dass Fehler erwartbar behandelt werden und dass der Nutzer keine falschen Daten für gesicherte Wahrheit hält. Eine App darf offline alte Daten anzeigen, wenn sie das bewusst tut. Kritisch wird es, wenn sie alte Daten wie frische Daten wirken lässt oder fehlgeschlagene Aktualisierungen verschweigt.
Wie funktioniert es?
Eine Data Reliability Review beginnt mit dem Datenvertrag. Du beschreibst für ein Feature, welche Datenquelle maßgeblich ist, welche lokalen Daten existieren, wann synchronisiert wird und wie Fehler nach oben gemeldet werden. Dieser Vertrag sollte nicht nur im Kopf einer Entwicklerin existieren. Er sollte sich in Typen, Zuständen, Tests und Code-Reviews wiederfinden.
Ein typisches Repository liefert zum Beispiel einen Flow aus UI-nahen Daten. Dieser Flow kann aus Room kommen, während im Hintergrund ein Netzwerk-Refresh läuft. Das ist ein normales Offline-First-Muster: Die App bleibt benutzbar, weil lokale Daten sofort verfügbar sind. Gleichzeitig muss sie aber kennzeichnen können, ob die Daten frisch sind, ob gerade aktualisiert wird und ob die letzte Aktualisierung fehlgeschlagen ist.
Daraus ergeben sich konkrete Prüffragen. Was passiert beim ersten App-Start ohne Netzwerk? Was passiert, wenn lokale Daten vorhanden sind, aber der Server nicht erreichbar ist? Was passiert, wenn der Server neuere Daten liefert, während der Nutzer auf dem Bildschirm ist? Was passiert, wenn eine Schreiboperation lokal erfolgreich aussieht, aber später nicht synchronisiert werden kann? Jede dieser Fragen prüft einen anderen Aspekt von Reliability.
Konsistenz ist dabei oft eine bewusste Entscheidung. In einer Banking-App darf ein Kontostand nicht stillschweigend aus einem alten Cache stammen. In einer Rezepte-App kann ein zwanzig Minuten alter Cache akzeptabel sein. In einer Aufgaben-App kann eine lokal neu erstellte Aufgabe sofort erscheinen, obwohl der Server sie noch nicht bestätigt hat. Diese Unterschiede sind keine technischen Details, sondern Produktregeln. Deine Android-Architektur muss sie ausdrücken können.
Freshness braucht messbare Kriterien. Ein boolesches isLoading reicht dafür selten. Du brauchst häufig Zeitstempel wie lastSyncedAt, einen Status wie isStale oder Regeln wie “nach 15 Minuten neu laden”. Ohne solche Kriterien entsteht ein unscharfes Verhalten: Manche Screens aktualisieren ständig, andere nie, und niemand kann erklären, warum ein bestimmter Datenstand angezeigt wird.
Fehler sollten ebenfalls als Teil des Datenmodells betrachtet werden. Eine Exception, die nur geloggt wird, hilft dem Nutzer nicht und ist schwer zu testen. Besser ist ein expliziter Zustand, etwa DataError.Network, DataError.Unauthorized oder DataError.Unknown. Du musst nicht jede technische Ursache bis in die UI reichen. Aber du solltest die Fehler so modellieren, dass ViewModel und UI sinnvoll reagieren können.
In Jetpack Compose wird Reliability besonders sichtbar, weil der Bildschirm direkt auf State reagiert. Wenn dein State nur aus items: List<Item> besteht, kann Compose nicht unterscheiden, ob die Liste leer ist, noch lädt, aus altem Cache stammt oder wegen eines Fehlers nicht aktualisiert werden konnte. Ein zuverlässiger State enthält deshalb Daten und Metadaten: Inhalt, Ladezustand, Freshness und Fehlerhinweis.
Auch Coroutines und Flow beeinflussen die Review. Du prüfst, ob laufende Aktualisierungen korrekt abgebrochen werden, ob Fehler in Flows nicht unkontrolliert den Stream beenden und ob Retry-Regeln bewusst gesetzt sind. Ein häufiger Fehler ist, einen Flow bei einer Exception enden zu lassen. Danach beobachtet die UI keine weiteren Änderungen mehr, obwohl lokale Daten später wieder verfügbar wären.
Die Review ist kein einzelner Test am Ende. Sie gehört in die tägliche Entwicklung: beim Entwurf eines Repositories, beim Schreiben eines ViewModels, beim Code-Review einer Synchronisierungslogik und vor einem Release. Besonders bei Offline-First-Features solltest du Reliability früh prüfen, weil spätere Korrekturen oft mehrere Schichten betreffen.
In der Praxis
Nimm als Beispiel eine Aufgabenliste. Die App speichert Aufgaben lokal in Room und lädt Änderungen vom Server. Der Bildschirm soll auch ohne Netzwerk nutzbar sein. Gleichzeitig darf er nicht so tun, als wären alte lokale Daten sicher aktuell. Ein Repository kann deshalb einen Datenzustand liefern, der mehr enthält als nur die Liste.
data class TaskListState(
val tasks: List<Task>,
val isRefreshing: Boolean,
val isStale: Boolean,
val error: DataError?
)
sealed interface DataError {
data object Network : DataError
data object Unauthorized : DataError
data object Unknown : DataError
}
class TaskRepository(
private val local: TaskDao,
private val remote: TaskApi,
private val clock: Clock
) {
fun observeTasks(): Flow<TaskListState> {
return local.observeTasksWithMetadata().map { localData ->
val staleAfter = Duration.ofMinutes(15)
TaskListState(
tasks = localData.tasks,
isRefreshing = false,
isStale = Duration.between(localData.lastSyncedAt, clock.instant()) > staleAfter,
error = null
)
}
}
suspend fun refreshTasks(): DataError? {
return try {
val remoteTasks = remote.getTasks()
local.replaceTasks(
tasks = remoteTasks,
syncedAt = clock.instant()
)
null
} catch (e: IOException) {
DataError.Network
} catch (e: HttpException) {
if (e.code() == 401) DataError.Unauthorized else DataError.Unknown
}
}
}
Das Beispiel ist absichtlich kompakt. In einer echten App würdest du den Refresh-Status oft im ViewModel zusammenführen, vielleicht mit StateFlow, und Fehlerereignisse sauberer modellieren. Entscheidend ist die Richtung: Die Datenebene liefert nicht nur rohe Daten, sondern Informationen über Vertrauen. Die UI kann dann zeigen, dass Daten aus dem Cache stammen, eine Aktualisierung läuft oder ein Netzwerkfehler vorliegt.
Eine praktische Entscheidungsregel lautet: Wenn ein Bildschirm Daten anzeigt, musst du beantworten können, aus welcher Quelle sie stammen, wie alt sie sind und was beim letzten Aktualisierungsversuch passiert ist. Wenn du diese drei Fragen nicht beantworten kannst, ist deine Datenlogik schwer zu prüfen. Dann brauchst du nicht sofort mehr Framework-Code, sondern klarere Zustände.
Eine typische Stolperfalle ist das Verschlucken von Fehlern. Viele Lernende schreiben Code wie catch (e: Exception) { emptyList() }. Das wirkt zunächst stabil, weil die App nicht abstürzt. In Wirklichkeit verlierst du aber Information. Eine leere Liste kann bedeuten: Es gibt keine Aufgaben. Sie kann auch bedeuten: Der Server ist nicht erreichbar. Für den Nutzer und für Tests sind das zwei völlig verschiedene Situationen.
Eine zweite Stolperfalle ist ein Cache ohne Freshness-Regel. Ein lokaler Cache ist nützlich, aber er braucht Grenzen. Wenn du nie speicherst, wann Daten zuletzt synchronisiert wurden, kannst du später nicht sauber entscheiden, ob ein Refresh nötig ist. Dann entstehen zufällige Aktualisierungen, doppelte Requests oder veraltete Anzeigen. Besonders in Compose fällt das auf, weil der Bildschirm schnell und zuverlässig das rendert, was dein State vorgibt. Wenn der State unklar ist, wirkt auch die UI unklar.
Eine dritte Stolperfalle betrifft parallele Änderungen. Stell dir vor, der Nutzer markiert eine Aufgabe lokal als erledigt, während ein Server-Refresh eine ältere Version zurückliefert. Ohne Konfliktregel kann die App die lokale Änderung überschreiben. In einer Review fragst du deshalb: Welche Quelle gewinnt? Gibt es Versionsnummern, Zeitstempel oder Serverbestätigung? Werden ausstehende lokale Änderungen besonders markiert? Du musst nicht jedes Konfliktsystem selbst bauen, aber du brauchst eine bewusste Regel.
Für den Alltag hilft eine kleine Review-Checkliste. Erstens: Prüfe jeden Screen mit leerem Cache und ausgeschaltetem Netzwerk. Zweitens: Prüfe denselben Screen mit vorhandenem Cache und fehlschlagendem Refresh. Drittens: Prüfe, ob ein Fehler sichtbar, testbar oder mindestens im State nachvollziehbar ist. Viertens: Prüfe im Code-Review, ob catch-Blöcke Information erhalten oder verstecken. Fünftens: Prüfe, ob Freshness nicht nur gefühlt, sondern über Daten wie lastSyncedAt beschrieben wird.
Tests können diese Review sehr konkret machen. Für ein Repository kannst du Fake-Datenquellen verwenden: Eine lokale Quelle liefert alte Daten, die Remote-Quelle wirft eine IOException, und dein Test prüft, ob der State weiterhin lokale Daten enthält und zusätzlich einen Netzwerkfehler meldet. Für ViewModels prüfst du, ob aus Repository-Zuständen passende UI-Zustände werden. Für Compose-Screens kannst du sicherstellen, dass Fehler- und Stale-Hinweise nicht nur theoretisch existieren, sondern gerendert werden.
Beim Debugging solltest du nicht nur Breakpoints in den erfolgreichen Pfad setzen. Simuliere langsame Verbindungen, HTTP-Fehler, leere Datenbanken und Prozessneustarts. Beobachte dann, ob deine Flows weiterlaufen, ob Zustände mehrfach oder in falscher Reihenfolge kommen und ob die UI bei schnellen Änderungen flackert. Reliability zeigt sich oft erst in diesen Zwischenzuständen.
Im Code-Review kannst du sehr gezielt fragen: Beendet diese Exception den Datenstrom? Wird hier alter Cache als frische Wahrheit dargestellt? Kann die UI zwischen “leer” und “nicht geladen” unterscheiden? Ist der Retry begrenzt oder läuft er endlos? Gibt es eine klare Stelle, an der Synchronisierung entschieden wird? Solche Fragen sind wertvoller als allgemeine Kommentare wie “Fehler besser behandeln”, weil sie konkrete Datenrisiken sichtbar machen.
Fazit
Eine Data Reliability Review hilft dir, Datenverhalten nicht nur im Erfolgsfall zu verstehen, sondern unter realen Fehlerbedingungen zu prüfen. Du achtest auf Konsistenz, Freshness und Fehler als zusammenhängenden Vertrag zwischen Repository, lokaler Quelle, Remote-Quelle, ViewModel und Compose-UI. Übe das an einem kleinen Feature: Schalte das Netzwerk ab, simuliere alte Cache-Daten, schreibe Repository-Tests für Fehlerfälle und prüfe im Code-Review jeden catch-Block darauf, ob er Vertrauen schafft oder wichtige Information versteckt.