Pattern di sincronizzazione in background con Kotlin WorkManager per app da campo
Pattern di sincronizzazione in background con Kotlin WorkManager per app da campo: scegli il tipo di lavoro giusto, imposta i vincoli, usa il backoff esponenziale e mostra il progresso visibile all'utente.

Cosa significa sincronizzazione in background affidabile per app da campo e ops
Nelle app da campo e per le operazioni, la sincronizzazione non è un "optional". È il modo in cui il lavoro esce dal dispositivo e diventa reale per il team. Quando la sincronizzazione fallisce, gli utenti se ne accorgono subito: un lavoro completato sembra ancora “in sospeso”, le foto spariscono o lo stesso rapporto viene caricato due volte creando duplicati.
Queste app sono più difficili delle tipiche app consumer perché i telefoni operano nelle condizioni peggiori. La rete passa tra LTE, Wi‑Fi debole e assenza di segnale. Il risparmio energetico blocca il lavoro in background. L'app viene uccisa, il sistema si aggiorna e i dispositivi si riavviano durante il tragitto. Una configurazione WorkManager affidabile deve sopravvivere a tutto questo senza drammi.
Affidabile solitamente significa quattro cose:
- Eventualmente consistente: i dati possono arrivare in ritardo, ma arrivano senza babysitting manuale.
- Recuperabile: se l'app muore a metà upload, la prossima esecuzione continua in sicurezza.
- Osservabile: utenti e support possono capire cosa sta succedendo e cosa è bloccato.
- Non distruttiva: i ritentativi non creano duplicati né corrompono lo stato.
“Esegui ora” si adatta ad azioni piccole e attivate dall'utente che dovrebbero terminare presto (per esempio inviare un singolo aggiornamento di stato prima che l'utente chiuda un lavoro). “Aspetta” è per lavori più pesanti come upload di foto, aggiornamenti batch o qualsiasi cosa che può consumare batteria o fallire su reti scarse.
Esempio: un ispettore invia un modulo con 12 foto in un seminterrato senza segnale. Una sincronizzazione affidabile salva tutto localmente, lo marca in coda e lo carica più tardi quando il dispositivo ha una connessione reale, senza che l'ispettore debba rifare il lavoro.
Scegli i blocchi di WorkManager giusti
Inizia scegliendo l'unità di lavoro più piccola e chiara. Questa decisione influisce sulla affidabilità più di qualsiasi logica di ritentativo intelligente.
One-time vs periodic work
Usa OneTimeWorkRequest per lavori che devono avvenire perché qualcosa è cambiato: un nuovo modulo è stato salvato, una foto ha finito la compressione o l'utente ha premuto Sincronizza. Enqueue immediatamente (con i vincoli) e lascia che WorkManager lo esegua quando il dispositivo è pronto.
Usa PeriodicWorkRequest per manutenzione continua, come un "controlla aggiornamenti" o una pulizia notturna. Il lavoro periodico non è preciso. Ha un intervallo minimo e può slittare in base alle regole di batteria e sistema, quindi non dovrebbe essere la tua unica via per upload importanti.
Un pattern pratico è usare one‑time work per il “deve sincronizzarsi presto” e il lavoro periodico come rete di sicurezza.
Scegliere Worker, CoroutineWorker o RxWorker
Se scrivi in Kotlin e usi funzioni suspend, preferisci CoroutineWorker. Mantiene il codice breve e la cancellazione si comporta come ti aspetti.
Worker va bene per codice bloccante semplice, ma devi fare attenzione a non bloccare troppo a lungo.
RxWorker ha senso solo se la tua app usa già pesantemente RxJava. Altrimenti è complessità in più.
Concatenare passi o avere un worker con fasi?
La catena è ottima quando i passaggi possono avere successo o fallire indipendentemente e vuoi ritentativi separati e log più chiari. Un worker con fasi può essere migliore quando i passaggi condividono dati e devono essere trattati come una singola transazione.
Una regola semplice:
- Catena quando i passaggi hanno vincoli diversi (upload solo su Wi‑Fi, poi una chiamata API leggera).
- Usa un worker quando ti serve una sincronizzazione “tutto o niente”.
WorkManager garantisce che il lavoro sia persistente, possa sopravvivere alla morte del processo e ai riavvii, e rispetti i vincoli. Non garantisce tempi esatti, esecuzione immediata o funzionamento dopo che l'utente forza l'arresto dell'app. Se stai costruendo un'app Android da campo (compresa una generata in Kotlin da AppMaster), progetta la sincronizzazione in modo che i ritardi siano sicuri e prevedibili.
Rendi la sincronizzazione sicura: idempotente, incrementale e riprendibile
Un'app da campo rieseguirà il lavoro. I telefoni perdono segnale, il sistema uccide i processi e gli utenti premono Sincronizza due volte perché non hanno visto nulla. Se la tua sincronizzazione in background non è sicura da ripetere, otterrai record duplicati, aggiornamenti mancanti o ritentativi infiniti.
Inizia rendendo ogni chiamata al server sicura da eseguire due volte. L'approccio più semplice è una chiave di idempotenza per elemento (per esempio una UUID memorizzata con il record locale) che il server tratta come “stessa richiesta, stesso risultato”. Se non puoi cambiare il server, usa una chiave naturale stabile e un endpoint upsert, oppure includi un numero di versione così il server può rifiutare update obsoleti.
Traccia lo stato locale in modo esplicito così il worker può riprendere dopo un crash senza indovinare. Una semplice macchina a stati spesso basta:
- queued
- uploading
- uploaded
- needs-review
- failed-temporary
Mantieni la sincronizzazione incrementale. Invece di "sincronizza tutto", conserva un cursore come lastSuccessfulTimestamp o un token rilasciato dal server. Leggi una piccola pagina di cambiamenti, applicali e poi avanza il cursore solo dopo che il batch è stato completamente salvato localmente. Batch piccoli (come 20–100 elementi) riducono i timeout, rendono il progresso visibile e limitano quanto lavoro devi ripetere dopo un'interruzione.
Rendi anche gli upload riprendibili. Per foto o payload grandi, conserva l'URI del file e i metadati di upload e marca come caricato solo dopo la conferma del server. Se il worker si riavvia, continua dallo stato conosciuto invece di ricominciare da zero.
Esempio: un tecnico compila 12 moduli e allega 8 foto sottoterra. Quando il dispositivo si riconnette, il worker carica a batch, ogni modulo ha una chiave di idempotenza e il cursore di sincronizzazione avanza solo dopo che ogni batch è riuscito. Se l'app viene uccisa a metà, rieseguire il worker completa gli elementi rimanenti senza duplicare nulla.
Vincoli che rispecchiano le condizioni reali dei dispositivi
I vincoli sono i guardrail che impediscono alla sincronizzazione in background di consumare batteria, bruciare piani dati o fallire nei momenti peggiori. Vuoi vincoli che rispecchino come si comportano i dispositivi sul campo, non sulla tua scrivania.
Inizia con un set piccolo che protegga gli utenti ma permetta comunque al lavoro di girare la maggior parte dei giorni. Una baseline pratica è: richiedi una connessione di rete, evita di eseguire quando la batteria è bassa e evita quando lo spazio di archiviazione è critico. Aggiungi "in carica" solo se il lavoro è pesante e non sensibile al tempo, perché molti dispositivi di campo sono raramente collegati durante il turno.
Sovra‑vincolare è una causa comune dei report “la sincronizzazione non parte mai”. Se richiedi Wi‑Fi non misurato, carica e batteria non bassa, stai chiedendo il momento perfetto che potrebbe non arrivare mai. Se il business ha bisogno dei dati oggi, è meglio eseguire lavori più piccoli più spesso che aspettare condizioni ideali.
I captive portal sono un altro problema reale: il telefono dice di essere connesso, ma l'utente deve premere “Accetta” su una pagina Wi‑Fi dell'hotel o pubblica. WorkManager non può rilevare quello stato in modo affidabile. Trattalo come un fallimento normale: tenta la sincronizzazione, fai timeout rapidamente e ritenta più tardi. Mostra anche un semplice messaggio in app come “Connesso a Wi‑Fi ma senza accesso a internet” quando puoi rilevarlo durante la richiesta.
Usa vincoli diversi per upload piccoli e grandi così l'app resta reattiva:
- Payload piccoli (status ping, metadata dei moduli): qualsiasi rete, batteria non bassa.
- Payload grandi (foto, video, mappe): rete non misurata quando possibile e considera la ricarica.
Esempio: un tecnico salva un modulo con 2 foto. Invia i campi del modulo su qualsiasi connessione, ma mette in coda gli upload delle foto in attesa di Wi‑Fi o di un momento migliore. L'ufficio vede il lavoro rapidamente e il dispositivo non consuma dati mobili caricando immagini in background.
Ritentativi con backoff esponenziale che non infastidiscono gli utenti
I ritentativi sono il punto in cui le app da campo o sembrano tranquille o sembrano rotte. Scegli una politica di backoff che si adatti al tipo di errore che ti aspetti.
Il backoff esponenziale è di solito il default più sicuro per le reti. Aumenta rapidamente i tempi di attesa così non bombardi il server o non consumi batteria quando la copertura è scarsa. Il backoff lineare può andare bene per problemi brevi e temporanei (per esempio una VPN instabile), ma tende a ritentare troppo spesso in aree con segnale debole.
Prendi decisioni di retry basate sul tipo di errore, non solo su “qualcosa è fallito”. Una semplice regola:
- Timeout di rete, 5xx, DNS, nessuna connettività:
Result.retry() - Auth scaduta (401): rinnova il token una volta, poi fallisci e chiedi all'utente di effettuare l'accesso
- Validazione o 4xx (bad request):
Result.failure()con un errore chiaro per il supporto - Conflitto (409) per elementi già inviati: trattalo come successo se la tua sincronizzazione è idempotente
Limita il danno in modo che un errore permanente non giri in loop. Imposta un numero massimo di tentativi e, dopo quello, arresta e mostra un unico messaggio silenzioso e azionabile (non notifiche ripetute).
Puoi anche cambiare comportamento con l'aumentare dei tentativi. Per esempio, dopo 2 fallimenti invia batch più piccoli o salta upload grandi fino al prossimo pull riuscito.
val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
30, TimeUnit.SECONDS
)
.build()
// in doWork()
if (runAttemptCount \u003e= 5) return Result.failure()
return Result.retry()
Questo mantiene i ritentativi educati: meno risvegli, meno interruzioni per l'utente e recupero più rapido quando la connessione torna.
Progresso visibile all'utente: notifiche, lavoro in foreground e stato
Le app da campo spesso sincronizzano quando l'utente se lo aspetta meno: in un seminterrato, su rete lenta, con batteria quasi scarica. Se la sincronizzazione influisce su ciò che l'utente aspetta (upload, invio rapporti, batch di foto), rendila visibile e facile da capire. Il lavoro silenzioso in background è ottimo per aggiornamenti piccoli e veloci. Qualsiasi cosa più lunga dovrebbe apparire onesta.
Quando è richiesto il lavoro in foreground
Usa l'esecuzione in foreground quando un job è di lunga durata, sensibile al tempo o chiaramente legato a un'azione utente. Su Android moderno, grandi upload possono essere fermati o ritardati a meno che non siano eseguiti in foreground. In WorkManager questo significa restituire un ForegroundInfo così il sistema mostra una notifica in corso.
Una buona notifica risponde a tre domande: cosa si sta sincronizzando, quanto manca e come fermarlo. Aggiungi un'azione di annullamento chiara così l'utente può rinunciare se è su rete a consumo o ha bisogno subito del telefono.
Progresso che gli utenti possono fidarsi
Il progresso dovrebbe essere misurato in unità reali, non percentuali vaghe. Aggiorna il progresso con setProgress e leggilo da WorkInfo nell'UI (o in una schermata di stato).
Se stai caricando 12 foto e 3 moduli, segnala “5 di 15 elementi caricati”, mostra cosa resta e conserva l'ultimo messaggio di errore per il supporto.
Mantieni il progresso significativo:
- Elementi completati e elementi rimanenti
- Passo corrente ("Uploading photos", "Sending forms", "Finalizing")
- Ultimo sync riuscito
- Ultimo errore (breve, leggibile dall'utente)
- Opzione visibile per annullare/fermare
Se il tuo team costruisce strumenti interni rapidamente con AppMaster, tieni la stessa regola: gli utenti si fidano della sync quando la vedono e quando corrisponde a ciò che stanno cercando di fare.
Unique work, tag e come evitare job duplicati
I job di sincronizzazione duplicati sono uno dei modi più semplici per consumare batteria, bruciare dati mobili e creare conflitti lato server. WorkManager ti offre due strumenti semplici per evitarlo: nomi di lavoro unici e tag.
Un buon default è trattare la “sync” come una sola corsia. Invece di mettere in coda un nuovo job ogni volta che l'app si sveglia, enqueue lo stesso unique work name. In questo modo non ottieni una tempesta di sync quando l'utente apre l'app, cambia la rete e un job periodico scatta nello stesso momento.
val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
.addTag("sync")
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)
Scegliere la policy è la decisione comportamentale principale:
KEEP: se una sync è già in esecuzione (o in coda), ignora la nuova richiesta. Usalo per la maggior parte dei pulsanti “Sync now” e trigger auto‑sync.REPLACE: cancella quella corrente e ricomincia da capo. Usalo quando gli input sono davvero cambiati, come l'utente che cambia account o progetto.
I tag sono il tuo strumento per controllo e visibilità. Con un tag stabile come sync, puoi annullare, interrogare lo stato o filtrare i log senza tracciare ID specifici. Questo è particolarmente utile per un'azione manuale “sync now”: puoi controllare se c'è già lavoro in corso e mostrare un messaggio chiaro invece di lanciare un altro worker.
Periodic e on‑demand sync non dovrebbero combattersi. Tienili separati ma coordinati:
- Usa
enqueueUniquePeriodicWork("sync_periodic", KEEP, ...)per il job schedulato. - Usa
enqueueUniqueWork("sync", KEEP, ...)per on‑demand. - Nel tuo worker, esci rapidamente se non c'è nulla da caricare o scaricare, così la run periodica resta economica.
- Opzionalmente, fai che il worker periodico metta in coda lo stesso one‑time unique sync, così tutto il lavoro reale avviene in un solo posto.
Questi pattern mantengono la sincronizzazione prevedibile: una sync alla volta, facile da cancellare e facile da osservare.
Passo dopo passo: una pipeline di sincronizzazione pratica
Una pipeline di sync affidabile è più semplice da costruire se la tratti come una piccola macchina a stati: gli elementi di lavoro vivono prima localmente e WorkManager li muove in avanti solo quando le condizioni sono giuste.
Una pipeline semplice che puoi rilasciare
-
Parti con tabelle locali di "coda". Conserva i metadata minimi necessari per riprendere: id elemento, tipo (modulo, foto, nota), stato (pending, uploading, done), contatore tentativi, ultimo errore e un cursore o revisione del server per i download.
-
Per un “Sync now” attivato dall'utente, metti in coda un
OneTimeWorkRequestcon vincoli che rispecchiano il mondo reale. Scelte comuni sono rete connessa e batteria non bassa. Se gli upload sono pesanti, richiedi anche la ricarica. -
Implementa un
CoroutineWorkercon fasi chiare: upload, download, riconciliazione. Mantieni ogni fase incrementale. Carica solo gli elementi marcati pending, scarica solo i cambiamenti dal cursore, poi riconcilia i conflitti con regole semplici (per esempio: server vince per campi di assegnazione, client vince per bozze locali). -
Aggiungi ritentativi con backoff, ma sii selettivo su cosa ritentare. Timeout e 500s devono ritentare. Un 401 (logout) dovrebbe fallire velocemente e dire all'UI cosa è successo.
-
Osserva
WorkInfoper guidare UI e notifiche. Usa aggiornamenti di progresso per fasi come “Uploading 3 di 10” e mostra un breve messaggio di errore che indica la prossima azione (ritenta, effettua il login, connettiti a Wi‑Fi).
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
Quando tieni la coda locale e le fasi del worker esplicite, ottieni un comportamento prevedibile: il lavoro può mettere in pausa, riprendere e spiegare se stesso all'utente senza indovinare cosa è successo.
Errori e trappole comuni (e come evitarle)
La sincronizzazione affidabile fallisce spesso a causa di poche scelte piccole che sembrano innocue in fase di test, poi si sfaldano sui dispositivi reali. L'obiettivo non è eseguire il più spesso possibile. È eseguire al momento giusto, fare il lavoro giusto e fermarsi pulitamente quando non può.
Trappole da tenere d'occhio
- Fare grandi upload senza vincoli. Se spingi foto o payload grandi su qualsiasi rete e a qualsiasi livello di batteria, gli utenti lo sentiranno. Aggiungi vincoli per il tipo di rete e batteria bassa e dividi il lavoro grande in chunk più piccoli.
- Ritentare ogni errore per sempre. Un 401, token scaduto o permesso mancante non sono problemi temporanei. Contrassegnali come fallimento duro, mostra un'azione chiara (ri‑login) e ritenta solo errori transitori come timeout.
- Creare duplicati per errore. Se un worker può girare due volte, il server vedrà duplicati a meno che le richieste non siano idempotenti. Usa un ID generato dal client per elemento e fai sì che il server tratti i repeat come aggiornamenti, non come nuove risorse.
- Usare lavoro periodico per bisogni near real‑time. Il lavoro periodico è meglio per la manutenzione, non per “sync now”. Per la sincronizzazione attivata dall'utente, metti in coda one‑time unique work e lascia che l'utente la inneschi quando serve.
- Segnalare “100%” troppo presto. Il completamento dell'upload non è lo stesso che i dati siano stati accettati e conciliati. Monitora il progresso per fasi (queued, uploading, server confirmed) e mostra “fatto” solo dopo la conferma.
Un esempio concreto: un tecnico invia un modulo con tre foto in un ascensore con segnale debole. Se inizi subito senza vincoli, gli upload si bloccano, i ritentativi impazzano e il modulo potrebbe essere creato due volte al riavvio dell'app. Se vincoli a una rete usabile, fai upload a passi e assegni a ogni modulo un ID stabile, lo scenario termina con un solo record server e un messaggio di progresso veritiero.
Checklist rapida prima del rilascio
Prima del rilascio, testa la sincronizzazione come la rompono gli utenti sul campo: segnale intermittente, batterie scariche e molti tocchi. Quello che sembra ok su un telefono dev può ancora fallire nel mondo reale se scheduling, ritentativi o reporting di stato sono sbagliati.
Esegui questi controlli almeno su un dispositivo lento e uno più recente. Tieni i log, ma osserva anche ciò che l'utente vede nell'UI.
- Nessuna rete, poi recupero: Avvia una sync con connettività spenta, poi riaccendila. Conferma che il lavoro è in coda (non fallisce subito) e riprende più tardi senza duplicare gli upload.
- Riavvio del dispositivo: Inizia una sync, riavvia a metà, poi riapri l'app. Verifica che il lavoro continui o sia ripianificato correttamente e che l'app mostri lo stato giusto (non bloccata su "syncing").
- Batteria bassa e spazio esaurito: Attiva risparmio energetico, scendi sotto la soglia di batteria bassa se possibile e riempi lo storage quasi completamente. Conferma che il job aspetta quando deve e poi continua quando le condizioni migliorano, senza consumare batteria in un loop di ritentativi.
- Trigger ripetuti: Premi più volte il pulsante "Sync" o avvia sync da più schermate. Dovresti comunque finire con una sola run logica di sync, non con un mucchio di worker paralleli che si contendono gli stessi record.
- Errori server spiegabili: Simula 500, timeout ed errori di auth. Controlla che i ritentativi facciano backoff e si fermino dopo un limite, e che l'utente veda un messaggio chiaro tipo "Impossibile raggiungere il server, ritenteremo" invece di un fallimento generico.
Se un test lascia l'app in uno stato poco chiaro, trattalo come bug. Gli utenti perdonano la sync lenta, ma non perdonano la perdita di dati o il non capire cosa è successo.
Scenario d'esempio: moduli offline e upload di foto in un'app da campo
Un tecnico arriva in un sito con copertura debole. Compila un modulo di servizio offline, scatta 12 foto e preme Invia prima di andarsene. L'app salva tutto localmente (per esempio in un database locale): un record per il modulo e un record per ogni foto con uno stato chiaro come PENDING, UPLOADING, DONE o FAILED.
Quando preme Invia, l'app mette in coda un job di sync unico così non crea duplicati se tocca due volte. Un setup comune è una chain di WorkManager che carica prima le foto (più grandi e lente) e poi invia il payload del modulo dopo che gli allegati sono confermati.
La sync parte solo quando le condizioni corrispondono alla vita reale. Per esempio, aspetta rete connessa, batteria non bassa e spazio sufficiente. Se il tecnico è ancora nel seminterrato senza segnale, nulla consumerà batteria girando in loop in background.
Il progresso è ovvio e user‑friendly. L'upload viene eseguito in foreground e mostra una notifica tipo “Uploading 3 di 12”, con una chiara azione Annulla. Se annulla, l'app interrompe il lavoro e mantiene gli elementi rimanenti in PENDING così possono essere ritentati più tardi senza perdere dati.
I ritentativi si comportano educatamente dopo un hotspot instabile: il primo fallimento ritenta presto, ma ogni fallimento successivo aspetta di più (backoff esponenziale). All'inizio sembra reattivo, poi rallenta per evitare di scaricare la batteria e spam della rete.
Per il team ops il guadagno è pratico: meno invii duplicati perché gli elementi sono idempotenti e in coda univoca, stati di errore chiari (quale foto è fallita, perché e quando ritenterà) e maggiore fiducia che “inviato” significhi “memorizzato al sicuro e sarà sincronizzato”.
Prossimi passi: rilascia l'affidabilità prima, poi amplia la portata della sync
Prima di aggiungere altre funzionalità di sync, chiarisci cosa significa “finito”. Per la maggior parte delle app da campo non è “richiesta inviata”. È “server accettato e confermato”, più uno stato UI che corrisponde alla realtà. Un modulo che dice “Synced” dovrebbe rimanere così dopo un riavvio dell'app, e un modulo che ha fallito dovrebbe mostrare cosa fare dopo.
Rendi l'app facile da fidare aggiungendo un piccolo set di segnali che le persone possono vedere (e di cui il supporto può chiedere). Mantienili semplici e coerenti tra le schermate:
- Ultima sincronizzazione riuscita
- Ultimo errore di sync (messaggio breve, non stack trace)
- Elementi in sospeso (per esempio: 3 moduli, 12 foto)
- Stato corrente di sync (Idle, Syncing, Needs attention)
Tratta l'osservabilità come parte della feature. Ti fa risparmiare ore sul campo quando qualcuno è in connessione debole e non sa se l'app sta funzionando.
Se stai costruendo anche backend e strumenti admin, generarli insieme aiuta a mantenere il contratto di sync stabile. AppMaster (appmaster.io) può generare un backend pronto per la produzione, un pannello admin web e app native mobili, il che aiuta a mantenere modelli e auth allineati mentre ti concentri sugli angoli complicati della sync.
Infine, esegui un piccolo pilot. Scegli una fetta end‑to‑end di sync (per esempio, “invia modulo di ispezione con 1–2 foto”) e rilasciala con vincoli, ritentativi e progresso visibile all'utente completamente funzionanti. Quando quella fetta diventa noiosa e prevedibile, amplia una funzionalità alla volta.
FAQ
La sincronizzazione affidabile significa che il lavoro creato sul dispositivo viene prima salvato localmente e poi caricato in seguito senza che l'utente debba ripetere i passaggi. Deve sopravvivere a chiusure dell'app, riavvii, reti instabili e ritentativi senza perdere dati o creare duplicati.
Usa work one‑time per qualsiasi evento reale come “modulo salvato”, “foto aggiunta” o quando l'utente tocca Sincronizza. Usa il lavoro periodico per manutenzione e come rete di sicurezza, ma non come unica via per upload importanti, perché la sua esecuzione può subire drift temporali.
Se usi Kotlin e le tue funzioni di sincronizzazione sono suspend, CoroutineWorker è la scelta più semplice e prevedibile, soprattutto per la cancellazione. Usa Worker solo per compiti bloccanti brevi e RxWorker solo se l'app è già basata su RxJava.
Esegui in catena i worker quando i passaggi hanno vincoli diversi o devono ritentare separatamente (per esempio: upload grandi solo su Wi‑Fi e poi una chiamata API leggera). Usa un unico worker con fasi chiare quando i passaggi condividono stato e vuoi un comportamento “tutto o niente” per una singola sincronizzazione logica.
Rendi ogni richiesta di crea/aggiorna sicura da eseguire più volte usando una chiave di idempotenza per elemento (spesso una UUID memorizzata con il record locale). Se non puoi cambiare il server, punta agli upsert con chiavi stabili o a controlli di versione così i ripetuti non generano nuove righe.
Mantieni stati locali espliciti come queued, uploading, uploaded e failed in modo che il worker possa riprendere senza indovinare. Segna un elemento come completato solo dopo la conferma del server e conserva metadata sufficienti (per esempio URI del file e contatori di tentativi) per riprendere dopo un crash o reboot.
Parti da vincoli minimi che proteggono gli utenti ma permettono comunque alla sincronizzazione di funzionare la maggior parte dei giorni: rete richiesta, evitare quando la batteria è bassa e evitare spazio di archiviazione critico. Attento con “unmetered” e “charging” perché possono impedire che la sincronizzazione avvenga mai su dispositivi di campo.
Tratta “connesso ma senza internet” come un fallimento normale: timeout rapidi, Result.retry() e riprova più tardi. Se puoi rilevarlo durante la richiesta, mostra un messaggio semplice così l'utente capisce perché il dispositivo sembra online ma la sincronizzazione non procede.
Per i guasti di rete usa il backoff esponenziale così i ritentativi diventano meno frequenti quando la copertura è scarsa. Ritenta su timeout e errori 5xx, fallisci rapidamente su problemi permanenti tipo richieste invalide e imposta un limite massimo di tentativi per non entrare in loop infiniti quando serve l'intervento dell'utente (per esempio effettuare il login).
Enqueue la sincronizzazione come work unico così più trigger non avviano job paralleli, e mostra un progresso che gli utenti possono fidarsi per upload lunghi. Se il lavoro è lungo o iniziato dall'utente, eseguilo in foreground con una notifica persistente che mostra conteggi reali e offre un'opzione chiara per annullare.


