20 apr 2025·8 min di lettura

Networking in Kotlin per connessioni lente: timeout e ritentativi sicuri

Networking pratico in Kotlin per connessioni lente: imposta timeout, cache sicure, ritenta senza duplicati e proteggi azioni critiche su reti mobili instabili.

Networking in Kotlin per connessioni lente: timeout e ritentativi sicuri

Cosa si rompe su connessioni lente e instabili

Su mobile, “lento” di solito non significa “nessuna connessione”. Spesso significa una connessione che funziona, ma solo a tratti. Una richiesta potrebbe impiegare 8–20 secondi, bloccarsi a metà e poi completarsi. Oppure può riuscire un momento e fallire il prossimo perché il telefono è passato dal Wi‑Fi all'LTE, è entrato in un'area a segnale basso o il sistema operativo ha messo l'app in background.

“Instabile” è peggio. Pacchetti persi, lookup DNS che scadono, handshake TLS che falliscono e connessioni che si resettano a caso. Puoi fare tutto “giusto” nel codice e vedere comunque errori in produzione perché la rete cambia sotto i tuoi piedi.

Qui i valori predefiniti tendono a rompersi. Molte app si affidano ai default delle librerie per timeout, retry e caching senza decidere cosa sia “abbastanza buono” per le persone reali. I default sono spesso tarati per Wi‑Fi stabile e API veloci, non per un treno pendolare, una corsa in ascensore o un bar affollato.

Gli utenti non descrivono “socket timeout” o “HTTP 503”. Notano sintomi: spinner infiniti, errori improvvisi dopo lunga attesa (poi funziona al tentativo successivo), azioni duplicate (due prenotazioni, due ordini, doppie addebiti), aggiornamenti persi e stati misti in cui l'interfaccia dice “fallito” ma il server ha effettivamente avuto successo.

Le reti lente trasformano piccole lacune di progettazione in problemi di fiducia e denaro. Se l'app non separa chiaramente “ancora invio” da “fallito” da “finito”, gli utenti toccano di nuovo. Se il client ritenta a occhi chiusi, può generare duplicati. Se il server non supporta l'idempotenza, una connessione traballante può produrre più scritture “di successo”.

Le “azioni critiche” sono tutto ciò che deve avvenire al massimo una volta e correttamente: pagamenti, invio del checkout, prenotare uno slot, trasferire punti, cambiare password, salvare un indirizzo di spedizione, inviare una richiesta o approvare qualcosa.

Un esempio realistico: qualcuno invia il checkout con LTE debole. L'app manda la richiesta, poi la connessione cade prima di ricevere la risposta. L'utente vede un errore, tocca “Paga” di nuovo e ora due richieste arrivano al server. Senza regole chiare, l'app non sa se ritentare, aspettare o fermarsi. L'utente non sa se riprovare.

Decidete le regole prima di regolare il codice

Quando le connessioni sono lente o instabili, la maggior parte dei bug nasce da regole poco chiare, non dall'HTTP client. Prima di toccare timeout, caching o retry, scrivete cosa significa “corretto” per la vostra app.

Iniziate con le azioni che non devono mai essere eseguite due volte. Sono solitamente azioni di denaro e account: creare ordine, addebitare carta, inviare payout, cambiare password, eliminare account. Se un utente preme due volte o l'app ritenta, il server dovrebbe comunque trattarlo come una richiesta singola. Se non potete garantirlo ancora, trattate quegli endpoint come “no auto-retry” finché non lo potete fare.

Poi decidete cosa può fare ogni schermo quando la rete è scarsa. Alcuni schermi possono essere utili offline (profilo noto, ordini precedenti). Altri dovrebbero diventare read‑only o mostrare uno stato “riprovare” chiaro (conteggi inventario, prezzi live). Mescolare queste aspettative porta a UI confuse e caching rischioso.

Impostate il tempo di attesa accettabile per azione basandovi su come pensano gli utenti, non su ciò che sembra ordinato nel codice. Il login può tollerare una breve attesa. L'upload di file richiede più tempo. Il checkout deve sentirsi veloce ma anche sicuro. Un timeout di 30 secondi può essere “affidabile” sulla carta e comunque sembrare rotto.

Infine, decidete cosa memorizzare sul dispositivo e per quanto. I dati cache aiutano, ma quelli obsoleti possono portare a scelte sbagliate (vecchi prezzi, idoneità scaduta).

Scrivete le regole in un posto accessibile a tutti (un README va bene). Tenetelo semplice:

  • Quali endpoint sono “da non duplicare” e richiedono gestione dell'idempotenza?
  • Quali schermi devono funzionare offline e quali sono read‑only quando offline?
  • Qual è il tempo massimo d'attesa per azione (login, refresh feed, upload, checkout)?
  • Cosa si può cacheare sul dispositivo e qual è il tempo di scadenza?
  • Dopo un fallimento, mostrate un errore, mettete in coda per dopo o richiedete retry manuale?

Una volta chiare queste regole, i valori di timeout, gli header di caching, la policy di retry e gli stati UI sono molto più facili da implementare e testare.

Timeout che rispecchiano le aspettative reali degli utenti

Le reti lente falliscono in modi diversi. Una buona configurazione di timeout non “sceglie un numero” a caso. Rispecchia ciò che l'utente sta cercando di fare e fallisce abbastanza velocemente da permettere all'app di riprendersi.

I tre timeout, in termini semplici:

  • Connect timeout: quanto attendi per stabilire la connessione al server (lookup DNS, TCP, TLS). Se fallisce, la richiesta non è mai partita davvero.
  • Write timeout: quanto attendi mentre invii il corpo della richiesta (upload, JSON grandi, uplink lento).
  • Read timeout: quanto attendi che il server invii i dati dopo che la richiesta è stata inviata. Questo si vede spesso su reti mobili inaffidabili.

I timeout dovrebbero riflettere lo schermo e la posta in gioco. Un feed può essere più lento senza danni reali. Un'azione critica dovrebbe completare o fallire in modo chiaro così l'utente decide cosa fare dopo.

Un punto di partenza pratico (aggiustate dopo aver misurato):

  • Caricamento liste (basso rischio): connect 5–10s, read 20–30s, write 10–15s.
  • Ricerca con digitazione: connect 3–5s, read 5–10s, write 5–10s.
  • Azioni critiche (alto rischio, come “Paga” o “Invia ordine”): connect 5–10s, read 30–60s, write 15–30s.

La consistenza conta più della perfezione. Se l'utente preme “Invia” e vede uno spinner per due minuti, premerà di nuovo.

Evitate il “caricamento infinito” aggiungendo un limite superiore anche nella UI. Mostrate progresso immediatamente, permettete di annullare, e dopo (ad esempio) 20–30 secondi mostrate “Sto ancora provando…” con opzioni per riprovare o controllare la connessione. Questo mantiene l'esperienza onesta anche se la libreria di rete sta ancora aspettando.

Quando scade un timeout, loggate abbastanza per analizzare pattern in seguito, senza registrare segreti. Campi utili includono il path URL (non tutta la query), metodo HTTP, stato (se presente), un breakdown temporale (connect vs write vs read se disponibile), tipo di rete (Wi‑Fi, cellulare, modalità aereo), dimensione approssimativa della richiesta/risposta e un request ID così potete correlare i log client con quelli server.

Una configurazione Kotlin semplice e consistente

Quando le connessioni sono lente, piccole incoerenze nella configurazione client diventano grandi problemi. Una baseline pulita aiuta a debugare più velocemente e dà a ogni richiesta le stesse regole.

Un client, una policy

Iniziate con un unico posto dove costruite il vostro HTTP client (spesso un OkHttpClient usato da Retrofit). Mettete le basi lì così ogni richiesta si comporta allo stesso modo: header di default (versione app, locale, token auth) e uno User‑Agent chiaro, timeout impostati una volta (non sparsi nelle chiamate), logging attivabile per il debug e una decisione unica sui retry (anche se è “nessun retry automatico”).

Ecco un piccolo esempio che mantiene la configurazione in un unico file:

val okHttp = OkHttpClient.Builder()
  .connectTimeout(10, TimeUnit.SECONDS)
  .readTimeout(20, TimeUnit.SECONDS)
  .writeTimeout(20, TimeUnit.SECONDS)
  .callTimeout(30, TimeUnit.SECONDS)
  .addInterceptor { chain ->
    val request = chain.request().newBuilder()
      .header("User-Agent", "MyApp/${BuildConfig.VERSION_NAME}")
      .header("Accept", "application/json")
      .build()
    chain.proceed(request)
  }
  .build()

val retrofit = Retrofit.Builder()
  .baseUrl(BASE_URL)
  .client(okHttp)
  .addConverterFactory(MoshiConverterFactory.create())
  .build()

Gestione centrale degli errori che mappa sui messaggi utente

Gli errori di rete non sono solamente “un'eccezione”. Se ogni schermo li gestisce a modo suo, gli utenti ricevono messaggi casuali.

Create un mapper che converta i fallimenti in un piccolo set di esiti user‑friendly: nessuna connessione/moda aereo, timeout, errore server (5xx), errore di validazione o auth (4xx) e un fallback sconosciuto.

Questo mantiene il copy dell'UI coerente (“Nessuna connessione” vs “Riprova”) senza divulgare dettagli tecnici.

Taggare e cancellare le richieste quando gli schermi si chiudono

Su reti instabili, le chiamate possono terminare in ritardo e aggiornare uno schermo che non c'è più. Rendete la cancellazione una regola standard: quando uno schermo si chiude, il suo lavoro si ferma.

Con Retrofit e Kotlin coroutines, cancellare il coroutine scope (ad esempio in un ViewModel) cancella la chiamata HTTP sottostante. Per chiamate non coroutine, tenete un riferimento a Call e chiamate cancel(). Potete anche taggare le richieste e cancellare gruppi di chiamate quando si esce da una feature.

Il lavoro in background non dovrebbe dipendere dalla UI

Qualsiasi cosa importante che deve completarsi (inviare un report, sincronizzare una coda, completare una submission) dovrebbe girare in uno scheduler pensato per questo. Su Android, WorkManager è la scelta comune perché può ritentare dopo e sopravvivere ai riavvii dell'app. Tenete leggere le azioni UI e passate i lavori più lunghi a job in background quando ha senso.

Regole di caching sicure per mobile

Crea strumenti interni affidabili
Costruisci tool interni e pannelli amministrativi che funzionano anche quando la rete è inaffidabile.
Vedi la piattaforma

Il caching può dare grandi benefici su connessioni lente perché riduce download ripetuti e rende gli schermi istantanei. Può anche essere un problema se mostra dati obsoleti al momento sbagliato, come un saldo conto vecchio o un indirizzo di consegna non più valido.

Un approccio sicuro è cacheare solo ciò che l'utente può tollerare leggermente obsoleto, e forzare controlli freschi per tutto ciò che impatta denaro, sicurezza o una decisione finale.

Nozioni base di Cache‑Control su cui fare affidamento

La maggior parte delle regole si riduce a pochi header:

  • max-age=60: puoi riutilizzare la risposta cache per 60 secondi senza chiedere al server.
  • no-store: non salvare questa risposta (ideale per token e schermate sensibili).
  • must-revalidate: se è scaduta, devi controllare con il server prima di riutilizzarla.

Su mobile, must-revalidate evita dati “silenziosamente errati” dopo un periodo offline. Se l'utente apre l'app dopo una corsa in metropolitana, vuoi una schermata veloce ma anche che l'app confermi cosa è ancora vero.

Refresh con ETag: veloce, economico e affidabile

Per endpoint di lettura, la validazione basata su ETag spesso è meglio di lunghi max-age. Il server invia un ETag con la risposta. Alla successiva richiesta, l'app invia If-None-Match con quel valore. Se nulla è cambiato, il server risponde 304 Not Modified, che è piccolo e veloce su reti deboli.

Questo funziona bene per liste di prodotti, dettagli profilo e schermate impostazioni.

Una semplice regola pratica:

  • Cacheate gli endpoint di “lettura” con breve max-age più must-revalidate, e supportate ETag quando possibile.
  • Non cacheate gli endpoint di “scrittura” (POST/PUT/PATCH/DELETE). Trattateli sempre come vincolati alla rete.
  • Usate no-store per tutto ciò che è sensibile (risposte di auth, passi di pagamento, messaggi privati).
  • Cacheate assets statici (icone, config pubblica) più a lungo, perché il rischio di obsolescenza è basso.

Mantenete le decisioni di caching coerenti in tutta l'app. Gli utenti notano incoerenze più di piccoli ritardi.

Retry sicuri senza peggiorare le cose

Distribuisci con fiducia
Distribuisci su AppMaster Cloud o sul tuo cloud, con una configurazione che rispecchia le condizioni mobili reali.
Inizia ora

I retry sembrano una soluzione facile, ma possono ritorcersi contro. Se ritenti le richieste sbagliate crei carico extra, consumi batteria e fai sentire l'app bloccata.

Iniziate ritentando solo i fallimenti che sono probabilmente temporanei. Una connessione caduta, un read timeout o un breve outage server possono riuscire al tentativo successivo. Una password errata, un campo mancante o un 404 no.

Una regola pratica:

  • Ritentare timeout e fallimenti di connessione.
  • Ritentare 502, 503 e talvolta 504.
  • Non ritentare 4xx (eccetto 408 o 429, se avete una regola di attesa chiara).
  • Non ritentare richieste che hanno già raggiunto il server e potrebbero essere in elaborazione.
  • Tenere bassi i retry (spesso 1–3 tentativi).

Backoff + jitter: meno ondate di retry

Se molti utenti incontrano lo stesso outage, i retry istantanei possono creare un'onda di traffico che rallenta il recupero. Usate backoff esponenziale (aspettate sempre più a lungo) e aggiungete jitter (un piccolo ritardo casuale) così i dispositivi non ritentano tutti in sincrono.

Ad esempio: aspettate circa 0.5s, poi 1s, poi 2s, con un random +/- 20% ogni volta.

Mettete un tetto al tempo totale di retry

Senza limiti, i retry possono intrappolare gli utenti in uno spinner per minuti. Scegliete un tempo massimo totale per l'intera operazione, inclusi tutti i wait. Molte app puntano a 10–20 secondi prima di fermarsi e mostrare un'opzione chiara per riprovare.

Adattate anche al contesto. Se qualcuno sta inviando un modulo, vuole una risposta rapida. Se una sincronizzazione in background fallisce, potete ritentare più tardi.

Non ritentate mai automaticamente azioni non idempotenti (come piazzare un ordine o inviare un pagamento) a meno che non abbiate protezioni come una chiave di idempotenza o un controllo server dei duplicati. Se non potete garantire la sicurezza, fallite in modo chiaro e lasciate che l'utente decida cosa fare.

Prevenire duplicati per azioni critiche

Su una connessione lenta o instabile, gli utenti toccano due volte. L'OS può ritentare in background. La vostra app può reinviare dopo un timeout. Se l'azione è “crea qualcosa” (ordine, invio di denaro, cambio password), i duplicati possono fare male.

L'idempotenza significa che la stessa richiesta dovrebbe produrre lo stesso risultato. Se la richiesta è ripetuta, il server non dovrebbe creare un secondo ordine. Dovrebbe restituire il primo risultato o dire “già fatto”.

Usate una chiave di idempotenza per ogni tentativo critico

Per azioni critiche, generate una chiave di idempotenza unica quando l'utente inizia il tentativo e inviatela con la richiesta (spesso come header Idempotency-Key, o un campo nel body).

Un flusso pratico:

  • Create una UUID come chiave di idempotenza quando l'utente preme “Paga”.
  • Salvatela localmente con un piccolo record: status = pending, createdAt, hash del payload della richiesta.
  • Inviate la richiesta con la chiave.
  • Quando ricevete una risposta di successo, segnate status = done e memorizzate l'ID risultato del server.
  • Se dovete ritentare, riutilizzate la stessa chiave, non una nuova.

Questa regola “riusa la stessa chiave” è ciò che impedisce addebiti accidentali doppi.

Gestire riavvii dell'app e gap offline

Se l'app viene uccisa a metà richiesta, il successivo avvio deve essere ancora sicuro. Salvate la chiave di idempotenza e lo stato della richiesta nel storage locale (ad esempio una riga di DB). Al riavvio, o ritentate con la stessa chiave o chiamate un endpoint di “check status” usando la chiave salvata o l'ID risultato del server.

Lato server, il contratto dovrebbe essere chiaro: quando riceve una chiave duplicata, dovrebbe rifiutare il secondo tentativo o restituire la risposta originale (stesso order ID, stessa ricevuta). Se il server non può ancora farlo, la prevenzione client dei duplicati non sarà mai completamente affidabile, perché l'app non può vedere cosa è successo dopo aver inviato la richiesta.

Un tocco utile per l'utente: se un tentativo è pendente, mostrate “Pagamento in corso” e disabilitate il pulsante fino a un risultato finale.

Pattern UI che riducono i reinvii accidentali

Rendi il checkout resiliente
Prototipa un flusso di pagamento più sicuro con logica backend amica dell'idempotenza e UI client pulita.
Costruisci checkout

Le reti lente non rompono solo le richieste. Cambiano il modo in cui le persone toccano. Quando lo schermo si blocca per due secondi, molti utenti pensano che non sia successo nulla e premono di nuovo. La vostra UI deve far sentire “un solo tap” affidabile anche con rete scarsa.

L'UI ottimistica è più sicura quando l'azione è reversibile o a basso rischio, come mettere un elemento nei preferiti, salvare una bozza o segnare un messaggio come letto. L'UI confermata è migliore per denaro, inventario, cancellazioni irreversibili e tutto ciò che può creare duplicati.

Un default buono per azioni critiche è uno stato pendente chiaro. Dopo il primo tap, cambiate subito il pulsante primario in “Invio in corso…”, disabilitatelo e mostrate una riga breve che spiega cosa sta succedendo.

Pattern che funzionano bene su reti instabili:

  • Disabilitare l'azione primaria dopo il tap e mantenerla disabilitata fino al risultato finale.
  • Mostrare uno stato “Pending” visibile con dettagli (importo, destinatario, quantità).
  • Aggiungere una vista “Attività recenti” così gli utenti possono confermare cosa hanno già inviato.
  • Se l'app viene messa in background, mantenere lo stato pending al ritorno.
  • Preferire un pulsante primario chiaro invece di più target cliccabili nella stessa schermata.

A volte la richiesta riesce ma la risposta va persa. Trattate questo come un esito normale, non come un errore che invoglia a ripetere. Invece di “Fallito, riprova”, mostrate “Non siamo ancora sicuri” e offrite un passo sicuro come “Controlla stato”. Se non potete controllare lo stato, mantenete il record pending localmente e dite all'utente che aggiornerete quando la connessione ritorna.

Rendete “Riprova” esplicito e sicuro. Mostratelo solo quando potete ripetere la richiesta riutilizzando lo stesso request ID lato client o la stessa chiave di idempotenza.

Esempio realistico: invio checkout instabile

Rilascia app native più velocemente
Crea app native Android e iOS che gestiscono connessioni lente con stati coerenti.
Costruisci mobile

Un cliente è su un treno con segnale intermittente. Aggiunge articoli al carrello e preme Paga. L'app deve essere paziente, ma anche non creare due ordini.

Una sequenza sicura:

  1. L'app crea un attempt ID client‑side e invia la richiesta di checkout con una chiave di idempotenza (per esempio, una UUID salvata con il carrello).
  2. La richiesta aspetta un connect timeout chiaro, poi un read timeout più lungo. Il treno entra in galleria e la chiamata scade.
  3. L'app ritenta una volta, ma solo dopo un breve ritardo e solo se non ha mai ricevuto una risposta dal server.
  4. Il server riceve la seconda richiesta e vede la stessa chiave di idempotenza, quindi restituisce il risultato originale invece di creare un nuovo ordine.
  5. L'app mostra lo schermo di conferma finale quando riceve la risposta di successo, anche se arriva dal retry.

Il caching segue regole rigide. Liste prodotti, opzioni di consegna e tabelle fiscali possono essere cacheate per un breve periodo (GET). La submission del checkout (POST) non viene mai cacheata. Anche se usate una cache HTTP, trattatela come aiuto read‑only per la navigazione, non come qualcosa che può “ricordare” un pagamento.

La prevenzione dei duplicati è un mix di scelte di rete e UI. Quando l'utente preme Paga, il pulsante viene disabilitato e la schermata mostra “Invio ordine...” con un solo pulsante Annulla. Se l'app perde rete, passa a “Sto ancora provando” e mantiene lo stesso attempt ID. Se l'utente forza la chiusura e riapre, l'app può riprendere controllando lo stato dell'ordine usando quell'ID, invece di chiedere di pagare di nuovo.

Checklist rapida e prossimi passi

Se la vostra app va “abbastanza bene” su Wi‑Fi d'ufficio ma si rompe su treni, ascensori o aree rurali, trattatelo come una soglia di rilascio. Questo lavoro riguarda meno codice ingegnoso e più regole chiare e ripetibili.

Checklist prima del rilascio:

  • Impostate timeout per tipo di endpoint (login, feed, upload, checkout) e testate su reti limitate o ad alta latenza.
  • Ritentate solo dove è veramente sicuro e limitateli con backoff (alcuni tentativi per le letture, di solito nessuno per le scritture).
  • Aggiungete una chiave di idempotenza per ogni scrittura critica (pagamenti, ordini, invii di moduli) così un retry o un doppio tap non può creare duplicati.
  • Rendete esplicite le regole di caching: cosa può essere servito obsoleto, cosa deve essere fresco e cosa non deve mai essere cacheato.
  • Rendete visibili gli stati: pending, failed e completed devono apparire diversi e l'app deve ricordare azioni completate dopo un riavvio.

Se uno di questi punti è “decideremo più tardi”, finirete con comportamenti casuali tra le schermate.

Prossimi passi per farlo diventare prassi

Scrivete una policy di rete di una pagina: categorie di endpoint, target di timeout, regole di retry e aspettative di caching. Fatela rispettare in un punto solo (interceptor, factory client condivisa o un piccolo wrapper) così ogni membro del team eredita lo stesso comportamento di default.

Poi fate un breve esercizio di duplicazione. Scegliete un'azione critica (come il checkout), simulate uno spinner congelato, forzate la chiusura dell'app, attivate/disattivate la modalità aereo e premete di nuovo il pulsante. Se non potete dimostrare che è sicuro, gli utenti troveranno il modo di romperlo.

Se volete applicare le stesse regole su backend e client senza cablarle tutte a mano, AppMaster (appmaster.io) può aiutare generando codice backend e nativo pronto per la produzione. Anche così, la cosa chiave è la policy: definite idempotenza, retry, caching e stati UI una volta e applicateli coerentemente su tutto il flusso.

FAQ

Qual è la prima cosa da fare prima di modificare timeout e retry?

Inizia definendo cosa significa “corretto” per ogni schermo e azione, specialmente tutto ciò che deve succedere al massimo una volta come pagamenti o ordini. Una volta chiare le regole, imposta timeout, retry, caching e stati UI per allinearsi a quelle regole invece di affidarti ai valori predefiniti delle librerie.

Quali sono i sintomi più comuni che gli utenti notano su reti lente o instabili?

Gli utenti vedono di solito spinner infiniti, errori dopo lunga attesa, azioni che funzionano al secondo tentativo o risultati duplicati come due ordini o doppie addebiti. Spesso la causa non è solo il segnale, ma regole poco chiare su retry e sul distinguere “pending” da “failed”.

Come dovrei considerare connect, read e write timeout su mobile?

Usa il connect timeout per quanto aspetti a stabilire la connessione, il write timeout per inviare il corpo della richiesta (upload) e il read timeout per attendere la risposta dopo l'invio. Un buon default è timeout più corti per letture a basso rischio e read/write più lunghi per invii critici, con un limite UI chiaro così gli utenti non restano bloccati per sempre.

Se posso impostare un solo timeout in OkHttp, quale dovrebbe essere?

Sì: se puoi impostarne solo uno, usa callTimeout per limitare l'intera operazione end-to-end ed evitare attese “infinite”. Poi aggiungi connect/read/write per un controllo più fine, specialmente per upload e body di risposta lenti.

Quali errori sono solitamente sicuri da ritentare e quali no?

Ritenta inizialmente solo fallimenti temporanei come cadute di connessione, problemi DNS e timeout, e talvolta 502/503/504. Evita i retry sui 4xx e non ritentare automaticamente le scritture a meno che non hai protezione di idempotenza, perché i retry possono creare duplicati.

Come aggiungere retry senza far sembrare l'app bloccata?

Usa un numero ridotto di retry (spesso 1–3) con backoff esponenziale e un po' di jitter per evitare ondate di retry sincronizzate. Inoltre metti un limite al tempo totale speso nei retry così l'utente ottiene un risultato chiaro invece di uno spinner che dura minuti.

Cos'è l'idempotenza e perché conta per pagamenti e ordini?

L'idempotenza significa che ripetere la stessa richiesta non dovrebbe creare un secondo risultato, quindi un doppio tap o un retry non doppieranno addebiti o prenotazioni. Per azioni critiche, invia una chiave di idempotenza per tentativo e riutilizzala per i retry in modo che il server possa restituire il risultato originale anziché crearne uno nuovo.

Come generare e memorizzare una chiave di idempotenza su Android?

Genera una chiave unica quando l'utente avvia l'azione, salvala localmente con un piccolo record “pending” e inviala con la richiesta. Se ritenti o l'app si riavvia, riusa la stessa chiave e riprova in modo sicuro o verifica lo stato, così non trasformi una singola intenzione dell'utente in due scritture sul server.

Quali regole di caching sono più sicure per le app mobili su connessioni inaffidabili?

Memorizza solo i dati che possono essere un po' obsoleti e richiedi controlli freschi per tutto ciò che riguarda denaro, sicurezza o decisioni finali. Per le letture, preferisci breve freschezza più revalidazione (ETag); per le scritture, non usare cache e applica no-store per risposte sensibili.

Quali pattern UI riducono i doppi tap e i resubmit accidentali su reti lente?

Disabilita il pulsante primario dopo il primo tap, mostra subito uno stato “Submitting…” e mantieni uno stato pending visibile che sopravvive a backgrounding o riavvii. Se la risposta potrebbe andare persa, non spingere l'utente a riprovare; mostra incertezza (“Non siamo sicuri ancora”) e offri un passo sicuro come controllare lo stato.

Facile da avviare
Creare qualcosa di straordinario

Sperimenta con AppMaster con un piano gratuito.
Quando sarai pronto potrai scegliere l'abbonamento appropriato.

Iniziare