Kotlin WorkManager: Hintergrund-Sync-Muster für Feld-Apps
Kotlin WorkManager: Muster für Hintergrund‑Sync in Feld‑Apps — wählen Sie den richtigen Work‑Typ, setzen Sie Constraints, nutzen Sie exponentielles Backoff und zeigen Sie sichtbaren Fortschritt an.

Was zuverlässiger Hintergrund-Sync für Feld- und Einsatz-Apps bedeutet
In Feld- und Einsatz-Apps ist Sync kein „nice to have“. Es ist der Weg, wie Arbeit das Gerät verlässt und für das Team real wird. Wenn Sync fehlschlägt, bemerken Nutzer das schnell: Ein abgeschlossener Auftrag sieht noch wie „ausstehend“ aus, Fotos verschwinden oder derselbe Bericht wird doppelt hochgeladen und erzeugt Duplikate.
Diese Apps sind schwieriger als typische Consumer-Apps, weil Telefone unter den schlechtesten Bedingungen arbeiten. Das Netz wechselt zwischen LTE, schwachem WLAN und keinem Signal. Energiesparmodi blockieren Hintergrundarbeit. Die App wird beendet, das OS aktualisiert sich und Geräte starten unterwegs neu. Ein zuverlässiges WorkManager-Setup muss das alles ohne Drama überleben.
Zuverlässig heißt meistens vier Dinge:
- Letztlich konsistent: Daten können spät eintreffen, aber sie kommen an, ohne manuelles Nachhelfen.
- Wiederherstellbar: stirbt die App mitten im Upload, setzt der nächste Lauf sicher fort.
- Beobachtbar: Nutzer und Support können sehen, was passiert und was steckt.
- Nicht-destruktiv: Wiederholungen erzeugen keine Duplikate und korrumpieren keinen Zustand.
„Jetzt ausführen“ passt zu kleinen, nutzergetriggerten Aktionen, die bald beendet sein sollten (z. B. das Senden eines einzelnen Statusupdates, bevor der Nutzer einen Auftrag schließt). „Warten“ passt zu schwererem Work wie Foto-Uploads, Batch-Updates oder allem, was Akku zieht oder bei schlechtem Netz ausfällt.
Beispiel: Ein Inspektor füllt ein Formular mit 12 Fotos in einem Keller ohne Signal aus. Ein zuverlässiger Sync speichert alles lokal, markiert es als in der Warteschlange und lädt später hoch, wenn das Gerät eine echte Verbindung hat — ohne dass der Inspektor die Arbeit erneut machen muss.
Die richtigen WorkManager-Bausteine auswählen
Beginne damit, die kleinste, klarste Arbeitseinheit zu wählen. Diese Entscheidung beeinflusst die Zuverlässigkeit mehr als jede clevere Retry-Logik später.
One-time vs. periodic work
Verwende OneTimeWorkRequest für Arbeit, die passieren soll, weil sich etwas geändert hat: ein neues Formular wurde gespeichert, ein Foto wurde fertig komprimiert oder der Nutzer hat auf Sync getippt. Enqueue es sofort (mit Constraints) und lass WorkManager es ausführen, wenn das Gerät bereit ist.
Verwende PeriodicWorkRequest für regelmäßige Wartung, wie „Auf Updates prüfen“ oder eine nächtliche Aufräumaufgabe. Periodische Arbeit ist nicht genau. Sie hat ein Mindestintervall und kann je nach Batterie- und Systemregeln schwanken, daher sollte sie nicht der einzige Weg für wichtige Uploads sein.
Ein praktisches Muster ist One‑time‑Work für „muss bald synchronisiert werden“ und periodische Arbeit als Sicherheitsnetz.
Worker, CoroutineWorker oder RxWorker wählen
Wenn du Kotlin nutzt und suspend-Funktionen schreibst, bevorzuge CoroutineWorker. Der Code bleibt kurz und Cancellation verhält sich wie erwartet.
Worker passt zu einfachem blockierendem Code, aber du musst vorsichtig sein, nicht zu lange zu blockieren.
RxWorker macht nur Sinn, wenn deine App ohnehin stark RxJava verwendet. Ansonsten ist es zusätzliche Komplexität.
Schritte verketten oder einen Worker mit Phasen nutzen?
Chaining ist großartig, wenn Schritte unabhängig Erfolg oder Misserfolg haben können und du separate Retries sowie klarere Logs willst. Ein Worker mit Phasen kann besser sein, wenn Schritte Daten teilen und wie eine Transaktion behandelt werden müssen.
Eine einfache Regel:
- Verketten, wenn Schritte unterschiedliche Constraints haben (z. B. Upload nur über Wi‑Fi, dann ein leichter API‑Call).
- Einen Worker nutzen, wenn du ein „Alles-oder-nichts“-Sync brauchst.
WorkManager garantiert, dass Arbeit persistent gespeichert wird, Prozessende und Neustarts überlebt und Constraints respektiert. Er garantiert nicht genaue Timingpunkte, sofortige Ausführung oder Ausführung nach einem Force‑Stop durch den Nutzer. Wenn du eine Android-Feld-App baust (auch eine, die als Kotlin aus AppMaster generiert wurde), gestalte Sync so, dass Verzögerungen sicher und erwartet sind.
Sync sicher machen: idempotent, inkrementell und resumable
Eine Feld‑App wird Arbeit mehrfach ausführen. Telefone verlieren Signal, das OS beendet Prozesse, und Nutzer tippen zweimal auf Sync, weil nichts passiert scheint. Wenn dein Hintergrund‑Sync nicht sicher wiederholt werden kann, bekommst du doppelte Datensätze, fehlende Updates oder endlose Retries.
Beginne damit, jeden Serveraufruf sicher zweimal ausführbar zu machen. Der einfachste Ansatz ist ein Idempotency‑Key pro Element (z. B. eine lokal gespeicherte UUID), den der Server als „gleiche Anfrage, gleiches Ergebnis“ behandelt. Wenn du den Server nicht ändern kannst, nutze einen stabilen natürlichen Schlüssel und ein Upsert‑Endpoint oder einen Versionsnummern‑Check, damit der Server veraltete Updates ablehnen kann.
Verfolge den lokalen Zustand explizit, damit der Worker nach einem Crash ohne Raten weitermachen kann. Eine einfache Zustandsmaschine reicht oft:
- queued
- uploading
- uploaded
- needs-review
- failed-temporary
Halte Sync inkrementell. Anstatt „alles synchronisieren“, speichere einen Cursor wie lastSuccessfulTimestamp oder ein vom Server ausgegebenes Token. Lese eine kleine Seite mit Änderungen, wende sie an und erhöhe den Cursor erst, nachdem das Batch vollständig lokal committet ist. Kleine Batches (z. B. 20–100 Items) reduzieren Timeouts, machen Fortschritt sichtbar und begrenzen, wie viel Arbeit du nach einer Unterbrechung wiederholen musst.
Mach auch Uploads resumable. Bei Fotos oder großen Payloads persistiere die File‑URI und Upload‑Metadaten und markiere erst nach Bestätigung durch den Server als hochgeladen. Startet der Worker neu, fährt er vom letzten bekannten Zustand fort, statt von vorn zu beginnen.
Beispiel: Ein Techniker füllt 12 Formulare aus und hängt 8 Fotos an, unterirdisch. Wenn das Gerät wieder verbunden ist, lädt der Worker in Batches hoch, jedes Formular hat einen Idempotency‑Key und der Sync‑Cursor wird nur nach Erfolg jedes Batches erhöht. Wird die App zwischendurch beendet, beendet ein erneuter Workerlauf die verbleibenden wartenden Items ohne Duplikate.
Constraints, die den realen Gerätebedingungen entsprechen
Constraints sind Leitplanken, die verhindern, dass Hintergrund‑Sync Akkus leersaugt, Datenpläne aufbraucht oder im schlechtesten Moment scheitert. Du willst Constraints, die widerspiegeln, wie Geräte im Feld wirklich arbeiten, nicht wie auf deinem Schreibtisch.
Fange mit einer kleinen Menge an Constraints an, die Nutzer schützt, aber die Aufgabe an den meisten Tagen laufen lässt. Eine praktische Basis ist: Netzwerkverbindung erforderlich, nicht bei niedrigem Akku ausführen und nicht, wenn der Speicher kritisch knapp ist. „Charging“ nur setzen, wenn die Arbeit schwer und nicht zeitkritisch ist, denn viele Feldgeräte sind während der Schicht selten angeschlossen.
Über‑Constraining ist ein häufiger Grund für „Sync läuft nie“-Berichte. Forderst du ungemessenes WLAN, Laden und Akku nicht niedrig, verlangst du einen perfekten Moment, der möglicherweise nie eintritt. Wenn das Geschäft heute Daten braucht, ist es besser, kleinere Arbeiten öfter laufen zu lassen, als auf ideale Bedingungen zu warten.
Captive Portale sind ein weiteres reales Problem: Das Telefon sagt, es sei verbunden, aber der Nutzer muss auf einer Hotel‑ oder öffentlichen Wi‑Fi‑Seite „Akzeptieren“ tippen. WorkManager kann diesen Zustand nicht zuverlässig erkennen. Behandle es als normalen Fehler: versuche den Sync, timeout schnell und versuche es später erneut. Zeige außerdem, wenn möglich, eine einfache In‑App‑Meldung wie „Mit Wi‑Fi verbunden, aber kein Internetzugang".
Nutze unterschiedliche Constraints für kleine vs. große Uploads, damit die App reaktionsfähig bleibt:
- Kleine Payloads (Status‑Pings, Form‑Metadaten): jedes Netzwerk, Akku nicht niedrig.
- Große Payloads (Fotos, Videos, Kartendaten): ungemessenes Netzwerk wenn möglich, und Ladezustand in Betracht ziehen.
Beispiel: Ein Techniker speichert ein Formular mit 2 Fotos. Sende die Formularfelder über jede Verbindung, aber queue die Foto‑Uploads für Wi‑Fi oder einen besseren Moment. Das Büro sieht den Auftrag schnell, und das Gerät verbraucht nicht mobile Daten für Bilder hochzuladen.
Retries mit exponentiellem Backoff, die Nutzer nicht nerven
Retries sind der Punkt, an dem Feld‑Apps entweder ruhig oder kaputt wirken. Wähle eine Backoff‑Policy, die zur Art des erwarteten Fehlers passt.
Exponentielles Backoff ist meist die sicherste Voreinstellung für Netzwerke. Es erhöht schnell die Wartezeit, sodass du den Server nicht bombardierst oder Akku leerst, wenn die Verbindung schlecht ist. Lineares Backoff kann bei kurzen, vorübergehenden Problemen passen (z. B. ein instabiles VPN), neigt aber in schwachen Netzen zu häufigen Wiederholungen.
Treffe Retry‑Entscheidungen basierend auf dem Fehlertyp, nicht nur „etwas ist fehlgeschlagen“. Eine einfache Regel hilft:
- Netzwerk‑Timeout, 5xx, DNS, keine Verbindung:
Result.retry() - Auth abgelaufen (401): Token einmal erneuern, danach scheitern und den Nutzer zur Anmeldung auffordern
- Validation oder 4xx (Bad Request):
Result.failure()mit einer klaren Fehlermeldung für den Support - Conflict (409) bei bereits gesendeten Items: als Erfolg behandeln, wenn dein Sync idempotent ist
Begrenze den Schaden, damit ein permanenter Fehler nicht endlos in einer Schleife läuft. Setze eine maximale Anzahl an Versuchen und beende danach, zeige eine dezente, handlungsorientierte Meldung (keine wiederholten Notifications).
Du kannst das Verhalten auch mit zunehmenden Versuchen ändern. Zum Beispiel nach zwei Fehlschlägen kleinere Batches senden oder große Uploads bis zur nächsten erfolgreichen Pull‑Operation überspringen.
val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
30, TimeUnit.SECONDS
)
.build()
// in doWork()
if (runAttemptCount \u003e= 5) return Result.failure()
return Result.retry()
Das hält Retries höflich: weniger Aufwachvorgänge, weniger Nutzerstörungen und schnellere Erholung, wenn die Verbindung zurückkehrt.
Für Nutzer sichtbarer Fortschritt: Notifications, Foreground Work und Status
Feld‑Apps synchronisieren oft, wenn der Nutzer es am wenigsten erwartet: im Keller, in einem langsamen Netz, mit fast leerem Akku. Wenn Sync beeinflusst, worauf der Nutzer wartet (Uploads, Berichte, Fotobatches), mache ihn sichtbar und verständlich. Stille Hintergrundarbeit ist gut für kleine, schnelle Updates. Alles Längere sollte ehrlich wirken.
Wann Foreground‑Arbeit nötig ist
Nutze Foreground‑Ausführung, wenn ein Job langläuft, zeitkritisch ist oder klar an eine Nutzeraktion gebunden ist. Auf modernen Android‑Versionen können große Uploads gestoppt oder verzögert werden, wenn du nicht im Vordergrund läufst. In WorkManager bedeutet das, ein ForegroundInfo zurückzugeben, damit das System eine andauernde Notification zeigt.
Eine gute Notification beantwortet drei Fragen: Was wird synchronisiert, wie weit ist es, und wie kann es gestoppt werden? Füge eine klare Abbrechen‑Aktion hinzu, damit der Nutzer aussteigen kann, wenn er sich im mobilen Netz befindet oder sein Telefon jetzt braucht.
Fortschritt, dem Leute vertrauen
Fortschritt sollte sich an realen Einheiten orientieren, nicht an vagen Prozenten. Aktualisiere Fortschritt mit setProgress und lese ihn über WorkInfo in deiner UI (oder einer Statusseite).
Wenn du 12 Fotos und 3 Formulare hochlädst, melde „5 von 15 Items hochgeladen“, zeige, was noch fehlt, und behalte die letzte Fehlermeldung für den Support.
Halte Fortschritt aussagekräftig:
- Erledigte Items und verbleibende Items
- Aktueller Schritt ("Uploading photos", "Sending forms", "Finalizing")
- Letzte erfolgreiche Sync‑Zeit
- Letzter Fehler (kurz, benutzerfreundlich)
- Sichtbare Abbruch-/Stopp‑Option
Wenn dein Team interne Tools schnell mit AppMaster baut, behalte dieselbe Regel: Nutzer vertrauen Sync, wenn sie ihn sehen können und er mit dem übereinstimmt, was sie tatsächlich erreichen wollen.
Unique Work, Tags und das Vermeiden doppelter Sync‑Jobs
Doppelte Sync‑Jobs sind eine der einfachsten Ursachen für Akkuverbrauch, Datenverbrauch und serverseitige Konflikte. WorkManager bietet zwei einfache Werkzeuge, um das zu verhindern: unique work names und tags.
Eine gute Standardentscheidung ist, „sync“ als eine einzelne Spur zu behandeln. Statt bei jedem Aufwachen der App einen neuen Job einzureichen, enqueuee denselben Unique‑Work‑Namen. So entsteht kein Sync‑Sturm, wenn der Nutzer die App öffnet, ein Netzwerkwechsel auftritt und ein periodischer Job gleichzeitig auslöst.
val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
.addTag("sync")
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)
Die Wahl der Policy bestimmt das Verhalten:
KEEP: Wenn ein Sync bereits läuft (oder in der Warteschlange ist), ignoriere die neue Anfrage. Nutze das für die meisten „Sync now“-Buttons und Auto‑Sync‑Trigger.REPLACE: Beende den aktuellen und starte neu. Nutze das, wenn sich die Inputs wirklich geändert haben, z. B. der Nutzer hat das Konto gewechselt oder ein anderes Projekt ausgewählt.
Tags sind dein Griff für Kontrolle und Sichtbarkeit. Mit einem stabilen Tag wie sync kannst du abbrechen, Status abfragen oder Logs filtern, ohne spezifische IDs zu verfolgen. Das ist besonders nützlich für ein manuelles „Sync now“: Du kannst prüfen, ob bereits Arbeit läuft und stattdessen eine klare Meldung anzeigen.
Periodischer und on‑demand Sync sollten sich nicht bekriegen. Halte sie getrennt, aber koordiniert:
- Nutze
enqueueUniquePeriodicWork("sync_periodic", KEEP, ...)für den geplanten Job. - Nutze
enqueueUniqueWork("sync", KEEP, ...)für on‑demand. - Lass den Worker schnell beenden, wenn nichts zum Hoch- oder Runterladen da ist, damit der periodische Lauf günstig bleibt.
- Optional kann der periodische Worker denselben One‑time Unique Sync enqueued, sodass die reale Arbeit an einer Stelle stattfindet.
Diese Muster halten Hintergrund‑Sync vorhersehbar: ein Sync zur Zeit, einfach abzubrechen und leicht zu beobachten.
Schritt für Schritt: eine praktische Background‑Sync‑Pipeline
Eine zuverlässige Sync‑Pipeline ist leichter zu bauen, wenn du sie wie eine kleine Zustandsmaschine behandelst: Arbeitselemente leben zuerst lokal und WorkManager bewegt sie nur vorwärts, wenn die Bedingungen stimmen.
Eine einfache Pipeline, die du ausliefern kannst
-
Beginne mit lokalen „Queue“-Tabellen. Speichere die kleinsten Metadaten, die du zum Fortsetzen brauchst: Item‑ID, Typ (Formular, Foto, Notiz), Status (pending, uploading, done), Attempt‑Anzahl, letzter Fehler und ein Cursor oder Server‑Revision für Downloads.
-
Für ein vom Nutzer getipptes „Sync now“ enqueuee ein
OneTimeWorkRequestmit Constraints, die zur Realität passen. Übliche Wahl: Netzwerk verbunden und Akku nicht niedrig. Bei schweren Uploads auch „charging“. -
Implementiere einen
CoroutineWorkermit klaren Phasen: upload, download, reconcile. Halte jede Phase inkrementell. Lade nur Items hoch, die als pending markiert sind, lade nur Änderungen seit deinem letzten Cursor herunter und gleiche Konflikte mit einfachen Regeln ab (z. B. server wins für Zuweisungsfelder, client wins für lokale Entwurfsnotizen). -
Füge Retries mit Backoff hinzu, sei aber wählerisch, was du wiederholst. Timeouts und 500er sollten wiederholt werden. Ein 401 (abgelaufen) sollte schnell fehlschlagen und der UI mitteilen, was passiert ist.
-
Beobachte
WorkInfo, um UI und Notifications zu steuern. Nutze Progress‑Updates für Phasen wie „Uploading 3 of 10“ und zeige eine kurze Fehlermeldung, die zur nächsten Aktion führt (erneut versuchen, anmelden, mit Wi‑Fi verbinden).
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
Wenn du die Queue lokal hältst und die Worker‑Phasen explizit machst, bekommst du vorhersehbares Verhalten: Arbeit kann pausieren, wiederaufgenommen werden und sich dem Nutzer erklären, ohne zu raten, was passiert ist.
Häufige Fehler und Fallen (und wie man sie vermeidet)
Zuverlässiger Sync scheitert meistens an ein paar kleinen Entscheidungen, die im Test harmlos aussehen und auf echten Geräten auseinanderfallen. Das Ziel ist nicht, so oft wie möglich zu laufen. Es ist, zur richtigen Zeit die richtige Arbeit zu tun und sauber zu stoppen, wenn es nicht möglich ist.
Fallen, auf die du achten solltest
- Große Uploads ohne Constraints machen. Wenn du Fotos oder große Payloads über jedes Netzwerk und bei niedrigem Akku hochlädst, werden Nutzer das spüren. Füge Constraints hinzu und teile große Arbeit in kleinere Stücke.
- Jeden Fehler ewig wiederholen. Ein 401, abgelaufenes Token oder fehlende Berechtigung sind keine temporären Probleme. Markiere sie als harte Fehler, zeige eine klare Handlung (erneut anmelden) und wiederhole nur echte transiente Probleme wie Timeouts.
- Aus Versehen Duplikate erzeugen. Wenn ein Worker zweimal laufen kann, sieht dein Server doppelte Erstellungen, sofern Anfragen nicht idempotent sind. Nutze eine stabile clientseitige ID pro Item und lass den Server Wiederholungen als Updates behandeln.
- Periodische Arbeit für near‑real‑time Bedürfnisse nutzen. Periodische Jobs sind gut für Wartung, nicht für „jetzt synchronisieren“. Verwende für nutzerinitiierte Syncs One‑time Unique Work.
- „100%“ zu früh melden. Upload‑Abschluss ist nicht dasselbe wie Datenakzeptanz und Reconciliation. Tracke Phasen (queued, uploading, server confirmed) und zeige „fertig“ erst nach Bestätigung.
Ein konkretes Beispiel: Ein Techniker sendet ein Formular mit drei Fotos in einem Aufzug mit schwachem Signal. Startest du sofort ohne Constraints, bleiben Uploads stecken, Retries steigen und das Formular kann beim Neustart doppelt erstellt werden. Mit passenden Netzwerk‑Constraints, schrittweisem Upload und stabiler Formular‑ID endet das Szenario mit einem sauberen Server‑Datensatz und einer ehrlichen Fortschrittsmeldung.
Kurze Checkliste vor dem Release
Teste Sync so, wie echte Feldnutzer ihn zerstören werden: wackelige Verbindung, leere Akkus und viel Tippen. Was auf einem Dev‑Telefon gut aussieht, kann in freier Wildbahn scheitern, wenn Planung, Retries oder Statusmeldungen nicht stimmen.
Führe diese Tests mindestens auf einem langsamen Gerät und einem neueren Gerät durch. Halte Logs, aber achte auch auf das, was Nutzer in der UI sehen.
- Kein Netzwerk, dann Wiederherstellung: Starte einen Sync ohne Verbindung, schalte dann das Netz ein. Bestätige, dass Arbeit in der Warteschlange steht (nicht sofort fehlschlägt) und später ohne Duplikate fortsetzt.
- Geräte‑Neustart: Beginne einen Sync, starte das Gerät neu, öffne die App wieder. Überprüfe, dass die Arbeit weiterläuft oder korrekt neu geplant wird und die App den richtigen Zustand zeigt (nicht ewig „syncing").
- Niedriger Akku und wenig Speicher: Aktiviere Energiesparmodus, reduziere Akku unter die Low‑Schwelle und fülle den Speicher fast. Bestätige, dass der Job wartet, wenn er sollte, und später ohne Akkuverbrauch in einer Retry‑Schleife fortfährt.
- Wiederholtes Auslösen: Tippe mehrfach auf „Sync“ oder löse Sync von mehreren Screens aus. Du solltest am Ende einen logischen Sync‑Lauf haben, nicht viele parallele Worker, die um dieselben Datensätze kämpfen.
- Serverfehler, die du erklären kannst: Simuliere 500er, Timeouts und Auth‑Fehler. Prüfe, dass Retries zurückgehen und nach einer Obergrenze stoppen und der Nutzer eine deutliche Meldung wie "Can’t reach server, will retry" sieht statt einer generischen Fehlermeldung.
Wenn ein Test die App in einen unklaren Zustand versetzt, behandle das als Bug. Nutzer verzeihen langsamen Sync, aber nicht Datenverlust oder Unklarheit darüber, was passiert ist.
Beispiel‑Szenario: Offline‑Formulare und Foto‑Uploads in einer Feld‑App
Ein Techniker kommt an einer Stelle mit schwacher Verbindung an. Er füllt ein Serviceformular offline aus, macht 12 Fotos und tippt auf Submit, bevor er weggeht. Die App speichert alles zuerst lokal (z. B. in einer lokalen Datenbank): ein Datensatz fürs Formular und je ein Datensatz pro Foto mit klarem Status wie PENDING, UPLOADING, DONE oder FAILED.
Beim Tippen auf Submit enqueued die App einen Unique‑Sync‑Job, damit bei zweimal tippen keine Duplikate entstehen. Eine übliche Struktur ist eine WorkManager‑Chain, die zuerst Fotos hochlädt (größer, langsamer) und danach die Formular‑Payload sendet, sobald Anhänge bestätigt sind.
Der Sync läuft nur, wenn Bedingungen zur Realität passen. Zum Beispiel wartet er auf Netzwerkverbindung, nicht niedrigen Akku und genug Speicher. Ist der Techniker noch im Keller ohne Signal, wird der Akku nicht durch eine Endlosschleife im Hintergrund geleert.
Der Fortschritt ist sichtbar und benutzerfreundlich. Der Upload läuft als Foreground‑Arbeit und zeigt eine Notification wie „Uploading 3 of 12“ mit einer klaren Cancel‑Aktion. Bricht der Nutzer ab, stoppt die App die Arbeit und behält die verbleibenden Items in PENDING, damit sie später ohne Datenverlust erneut versucht werden können.
Retries verhalten sich nach einem instabilen Hotspot höflich: der erste Fehler wird bald erneut versucht, aber bei jedem weiteren Fehler wächst die Wartezeit (exponentielles Backoff). Es fühlt sich zunächst responsiv an und geht dann in ein sparsameres Verhalten über, um Akku und Netzwerk zu schonen.
Für das Ops‑Team ist der Nutzen praktisch: weniger doppelte Einsendungen, weil Items idempotent und eindeutig gequeued sind, klare Fehlerzustände (welches Foto schlug fehl, warum und wann wird neu versucht) und mehr Vertrauen, dass „submitted“ wirklich „sicher gespeichert und wird synchronisiert“ bedeutet.
Nächste Schritte: zuerst Zuverlässigkeit liefern, dann den Sync‑Umfang erweitern
Bevor du mehr Sync‑Features hinzufügst, kläre, was „fertig“ bedeutet. Für die meisten Feld‑Apps heißt das nicht „Anfrage gesendet“. Es heißt „Server akzeptiert und bestätigt“, plus ein UI‑Zustand, der der Realität entspricht. Ein Formular, das „Synced“ sagt, sollte nach einem App‑Restart so bleiben; ein fehlgeschlagenes Formular sollte zeigen, was als Nächstes zu tun ist.
Mach die App vertrauenswürdig, indem du ein kleines Set sichtbarer Signale anbietest (die auch der Support abfragen kann). Halte sie simpel und konsistent über die Screens hinweg:
- Letzte erfolgreiche Sync‑Zeit
- Letzter Sync‑Fehler (kurze Meldung, kein Stacktrace)
- Ausstehende Items (z. B. 3 Formulare, 12 Fotos)
- Aktueller Sync‑Zustand (Idle, Syncing, Needs attention)
Behandle Observability als Teil des Features. Das spart Stunden im Feld, wenn jemand bei schwacher Verbindung nicht weiß, ob die App funktioniert.
Wenn du Backend und Admin‑Tools ebenfalls baust, hilft es, sie zusammen zu erzeugen. AppMaster (appmaster.io) kann einen produktionsreifen Backend‑Stack, ein Web‑Admin‑Panel und native Mobile‑Apps generieren, was hilft, Modelle und Auth konsistent zu halten, während du die schwierigeren Sync‑Ecken löst.
Zum Schluss: Führe einen kleinen Pilotversuch durch. Wähle eine Ende‑zu‑Ende‑Sync‑Strecke (z. B. „Inspektionsformular mit 1–2 Fotos senden“) und liefere sie mit Constraints, Retries und sichtbarem Fortschritt aus. Wenn diese Strecke langweilig und vorhersagbar geworden ist, erweitere Funktion für Funktion.
FAQ
Zuverlässiger Hintergrund-Sync bedeutet, dass auf dem Gerät erzeugte Arbeit zuerst lokal gespeichert wird und später hochgeladen wird, ohne dass der Benutzer Schritte wiederholen muss. Er sollte App-Abstürze, Neustarts, schlechte Netze und Wiederholungen überstehen, ohne Daten zu verlieren oder Duplikate zu erzeugen.
Verwende OneTimeWorkRequest für alles, was durch ein echtes Ereignis ausgelöst wird („Formular gespeichert“, „Foto hinzugefügt“, Benutzer tippt auf Sync). Periodische Arbeit ist nützlich für Wartung und als Sicherheitsnetz, sollte aber nicht der einzige Weg für wichtige Uploads sein, da ihre Ausführungszeit schwanken kann.
Wenn du in Kotlin arbeitest und deine Sync-Logik suspend-Funktionen nutzt, ist CoroutineWorker die einfachste und vorhersagbarste Wahl, insbesondere für Cancellation. Worker nur für kurze blockierende Aufgaben; RxWorker nur, wenn die App ohnehin stark RxJava verwendet.
Arbeite mit Chains, wenn Schritte unterschiedliche Constraints haben oder separat wiederholt werden sollen (z. B. große Dateien nur über Wi‑Fi hochladen, dann eine API‑Abfrage). Nutze einen einzelnen Worker mit klaren Phasen, wenn die Schritte gemeinsamen Zustand teilen und du ein „Alles-oder-nichts“-Verhalten willst.
Mach jede Create-/Update-Anfrage so sicher, dass sie zweimal ausgeführt werden kann — z. B. mit einem Idempotency-Key pro Element (UUID, die lokal gespeichert wird). Wenn du den Server nicht ändern kannst, nutze stabile Keys mit Upserts oder Versionsprüfungen, damit Wiederholungen keine neuen Einträge erzeugen.
Persistiere klare lokale Status wie queued, uploading, uploaded, failed, damit der Worker nach einem Absturz ohne Raten weitermachen kann. Markiere ein Element erst als erledigt, wenn der Server den Upload bestätigt hat, und speichere genügend Metadaten (z. B. File‑URI, Versuchszähler), um einen Neustart fortzusetzen.
Beginne mit minimalen Constraints, die die Nutzer schützen, aber Sync noch täglich erlauben: Netzwerkverbindung erforderlich, nicht bei niedrigem Akku, nicht bei kritisch wenig Speicher. Sei vorsichtig mit „unmetered“ und „charging“, weil viele Feldgeräte selten angeschlossen sind.
Behandle „verbunden aber keine Internetverbindung“ als normalen Fehler: kurze Timeouts, Result.retry() und späteres erneutes Versuchen. Wenn du es während der Anfrage erkennen kannst, zeige eine einfache Meldung wie „Mit Wi‑Fi verbunden, aber kein Internetzugang“, damit Nutzer verstehen, warum Sync nicht vorankommt.
Nutze exponentielles Backoff für Netzwerkfehler, damit Wiederholungen seltener werden, wenn die Verbindung schlecht ist. Wiederhole Timeouts und 5xx‑Fehler, scheitere schnell bei permanenten Problemen (z. B. 4xx) und begrenze die Anzahl der Versuche, damit ein Benutzer eingreifen kann, wenn nötig.
Enqueuee Sync als Unique Work, damit mehrere Auslösungen nicht parallel laufen, und zeige zuverlässigen Fortschritt für lange Uploads. Wenn die Arbeit lang läuft oder vom Benutzer initiiert wurde, führe sie als Foreground-Arbeit mit einer andauernden Notification aus, die echte Zähler und eine klare Abbruchoption zeigt.


