Android Coden
Android 7 min lesen

Offline Write Queue

Eine Offline Write Queue sichert Nutzeraktionen ohne Netz. Du lernst, wie pending writes später per Sync und Retry übertragen werden.

Eine Offline Write Queue hilft dir, Nutzeraktionen auch dann ernst zu nehmen, wenn das Gerät gerade kein Netz hat. Statt einen Kommentar, eine Checkbox oder eine Formularänderung zu verlieren, speichert deine App den Schreibvorgang lokal als pending write und synchronisiert ihn später per Sync und Retry mit dem Backend.

Was ist das?

Eine Offline Write Queue ist eine lokale Warteschlange für Schreiboperationen. Du speicherst nicht nur den fertigen Datensatz, sondern die Absicht des Nutzers: „Dieser Kommentar soll erstellt werden“, „diese Aufgabe soll erledigt werden“, „dieser Profilwert soll geändert werden“. Solange die App nicht sicher mit dem Server sprechen kann, bleibt diese Absicht lokal erhalten.

Das mentale Modell ist einfach: Lesen und Schreiben sind in einer Offline-First-App nicht dasselbe. Gelesene Daten liegen oft als Cache in Room oder DataStore. Schreibende Aktionen brauchen zusätzlich eine robuste Spur, damit sie später nachgeholt werden können. Diese Spur ist die Queue. Jeder Eintrag enthält typischerweise eine ID, den Typ der Aktion, die Nutzdaten, einen Status, einen Zeitstempel und Informationen zu bisherigen Versuchen.

Im modernen Android-Kontext gehört diese Logik in die Data Layer, nicht direkt in eine Compose-Funktion. Deine UI löst eine Aktion aus, ein Repository nimmt sie entgegen, speichert sie lokal und startet oder plant die Synchronisation. Compose beobachtet danach den lokalen Zustand. So fühlt sich die App reaktionsschnell an, auch wenn der Netzwerkteil noch aussteht.

Wichtig ist die Grenze des Themas: Eine Offline Write Queue ist kein allgemeiner Cache und kein vollständiges Konfliktlösungs-System. Sie beantwortet zuerst die Frage: Wie bewahrst du lokale Schreibaktionen sicher auf, bis sie übertragen werden können?

Wie funktioniert es?

Der Ablauf besteht aus vier Schritten. Erstens nimmt die App die Nutzeraktion entgegen. Zweitens speichert sie die Änderung lokal, damit sie nach Prozessende, App-Neustart oder Geräte-Neustart nicht verloren geht. Drittens markiert sie den Eintrag als pending. Viertens versucht ein Sync-Mechanismus, den Eintrag an den Server zu senden. Schlägt das fehl, bleibt der Eintrag in der Queue und wird später erneut versucht.

Für persistente Queues ist Room oft die passende Wahl, weil du strukturierte Einträge ablegen, abfragen und transaktional ändern kannst. Für sehr kleine Flags kann DataStore genügen, aber für mehrere pending writes mit Status, Reihenfolge und Retry-Zähler ist eine Datenbank meist klarer. Die eigentlichen Netzwerkaufrufe liegen in einer Data Source, während das Repository entscheidet, wann ein lokaler Queue-Eintrag erzeugt und wann er als synchronisiert markiert wird.

Ein Queue-Eintrag braucht mindestens drei Zustände: pending, inProgress und synced oder failed. Pending bedeutet: Der Schreibvorgang wartet. InProgress bedeutet: Ein Worker oder Sync-Prozess versucht gerade die Übertragung. Synced bedeutet: Der Server hat die Operation akzeptiert, und der Eintrag kann gelöscht oder archiviert werden. Failed kann sinnvoll sein, wenn ein Fehler dauerhaft ist, etwa ein Validierungsfehler vom Server. Temporäre Fehler wie Timeout, 503 oder fehlendes Netz sollten dagegen zu Retry führen.

Retry ist mehr als „noch einmal probieren“. Du brauchst Regeln. Ein sinnvoller Retry wartet zwischen Versuchen, vermeidet parallele Doppelübertragungen und bricht bei dauerhaften Fehlern ab. WorkManager passt gut zu vielen Android-Fällen, weil er Arbeit unter Bedingungen wie Netzwerkverfügbarkeit planen kann. Trotzdem ersetzt WorkManager nicht deine Queue. Er startet nur den Job; die Queue bleibt die Quelle der Wahrheit.

Ein weiteres Kernkonzept ist Idempotenz. Wenn dein Sync nach einem Timeout wiederholt wird, weiß die App oft nicht, ob der Server die Anfrage verarbeitet hat. Darum sollte jede Operation eine stabile clientseitige ID besitzen. Der Server kann dann erkennen: Diese Aktion kenne ich bereits. Ohne solche IDs erzeugt ein Retry schnell doppelte Kommentare, doppelte Bestellungen oder mehrfach gezählte Ereignisse.

Für die UI bedeutet das: Du zeigst bevorzugt den lokalen Zustand an. Wenn der Nutzer eine Aufgabe abhakt, sieht er sie sofort als erledigt. Gleichzeitig kann deine App einen kleinen Sync-Status anzeigen oder intern für Debugging nachvollziehbar halten. Für Lernende ist wichtig: Die UI wartet nicht auf das Netzwerk, aber sie darf Fehler nicht verschweigen, wenn eine Aktion dauerhaft nicht übertragen werden kann.

In der Praxis

Stell dir eine Aufgaben-App vor. Der Nutzer markiert eine Aufgabe offline als erledigt. Die App soll diese Änderung sofort lokal anzeigen und später an das Backend senden. Eine mögliche Room-Entity für die Queue könnte so aussehen:

@Entity(tableName = "write_queue")
data class WriteQueueEntry(
    @PrimaryKey val id: String,
    val operation: String,
    val payload: String,
    val status: String,
    val retryCount: Int,
    val createdAt: Long,
    val lastError: String?
)

class TaskRepository(
    private val taskDao: TaskDao,
    private val queueDao: WriteQueueDao,
    private val syncScheduler: SyncScheduler
) {
    suspend fun markTaskDone(taskId: String) {
        val queueId = "mark-done-$taskId"

        database.withTransaction {
            taskDao.setDone(taskId = taskId, done = true)

            queueDao.insert(
                WriteQueueEntry(
                    id = queueId,
                    operation = "MARK_TASK_DONE",
                    payload = """{"taskId":"$taskId"}""",
                    status = "pending",
                    retryCount = 0,
                    createdAt = System.currentTimeMillis(),
                    lastError = null
                )
            )
        }

        syncScheduler.schedule()
    }
}

Der wichtige Punkt ist die Transaktion. Lokaler Zustand und Queue-Eintrag werden zusammen gespeichert. Wenn nur die Aufgabe geändert wird, aber kein Queue-Eintrag entsteht, verliert die App die spätere Synchronisation. Wenn nur der Queue-Eintrag entsteht, aber die lokale UI den neuen Zustand nicht kennt, fühlt sich die App träge oder widersprüchlich an.

Der Sync-Prozess liest pending writes aus der Datenbank, markiert einen Eintrag als inProgress und sendet ihn an die API. Bei Erfolg wird der Eintrag als synced markiert oder gelöscht. Bei einem Netzwerkfehler wird retryCount erhöht und der Eintrag wieder auf pending gesetzt. Bei einem fachlichen Fehler, etwa „Aufgabe existiert nicht mehr“, sollte der Eintrag nicht endlos wiederholt werden. Dann brauchst du eine klare Fehlerbehandlung: lokal zurückrollen, Nutzer informieren oder einen Konfliktzustand speichern.

Eine praktische Entscheidungsregel lautet: Speichere nie nur den aktuellen Screen-Zustand, wenn du eine spätere Serveraktion brauchst. Speichere eine konkrete Operation mit stabiler ID. „Task 42 ist erledigt“ kann als Zustand reichen, solange die Server-API genau diesen Zustand setzen kann. „Kommentar mit Text X wurde erstellt“ braucht dagegen eine clientseitige Kommentar-ID, damit ein Retry keinen zweiten Kommentar erzeugt.

Eine typische Stolperfalle ist die doppelte Ausführung. Viele Lernende starten bei jeder App-Öffnung, jedem Button-Klick und jeder Netzwerkänderung einen Sync. Wenn diese Läufe parallel dieselben pending writes lesen, entstehen doppelte Requests oder unklare Statuswechsel. Verhindere das durch Datenbankstatus, eindeutige IDs und eine Sync-Implementierung, die Einträge atomar reserviert. In Code-Reviews solltest du gezielt fragen: Kann derselbe Queue-Eintrag gleichzeitig von zwei Stellen gesendet werden?

Eine zweite Stolperfalle ist zu frühes Löschen. Lösche einen pending write erst, wenn du eine eindeutige Bestätigung vom Server hast oder sicher weißt, dass die Operation nicht mehr gesendet werden darf. Ein bloßer Start des Requests reicht nicht. Android-Prozesse können beendet werden, Funkverbindungen wechseln, und HTTP-Aufrufe können in unklaren Zuständen enden.

Beim Testen kannst du mit Fake-APIs arbeiten. Simuliere „kein Netz“, „Timeout beim ersten Versuch“, „Erfolg beim zweiten Versuch“ und „dauerhafter 400-Fehler“. Prüfe danach die Datenbank: Bleibt der pending write erhalten? Wird retryCount korrekt erhöht? Verschwindet der Eintrag nach Erfolg? Sieht die UI den lokalen Zustand sofort? Diese Tests sind wertvoller als ein einzelner Happy-Path-Test mit stabiler Verbindung.

Fazit

Eine Offline Write Queue ist ein kleines, aber entscheidendes Bauteil für verlässliche Offline-First-Apps: Du speicherst Nutzeraktionen als pending writes, synchronisierst sie später kontrolliert und behandelst Retry als geplanten Teil deiner Architektur. Prüfe dein Verständnis praktisch, indem du in einer Beispiel-App eine Schreibaktion offline ausführst, den Prozess beendest, die App neu startest und dann den Sync mit Debugger oder Tests verfolgst. Achte besonders darauf, ob die Queue nach Fehlern korrekt weiterlebt, ob erfolgreiche Einträge sauber verschwinden und ob ein Retry keine doppelten Serveränderungen erzeugt.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

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