Nello sviluppo mobile, la gestione dei dati, la memorizzazione nella cache e la sincronizzazione sono tre aspetti chiave che determinano le prestazioni e l'affidabilità dell'applicazione. Secondo la Google Android Architecture Guide, un'architettura di gestione dati corretta influisce direttamente sulla velocità di risposta e sull'esperienza utente. Il pattern Repository fornisce un punto di accesso unico a tutte le fonti di dati.
Punti Chiave
Il pattern Repository è un approccio architetturale in cui un'unica classe repository gestisce tutte le operazioni sui dati, astraendo le API REST remote e l'archiviazione locale Room o SwiftData. Questo modo di gestire i dati consente all'applicazione di ottenere informazioni prima da Memory Cache o Disk Cache, e poi dalla rete, riducendo i tempi di risposta. Nello sviluppo mobile, Repository è diventato lo standard de facto grazie alle raccomandazioni di Google e Apple.
Remote Data Source fornisce informazioni aggiornate dal server tramite richieste HTTP. Local Data Source è l'archiviazione locale sul dispositivo, implementata tramite Room su Android o SwiftData su iOS. Il repository combina entrambe le fonti: prima controlla la cache locale e, in assenza di dati, richiede dall'API remota. Questa organizzazione della gestione dati consente all'applicazione di funzionare in modalità offline e riduce il carico del server.
class UserRepository(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource
) {
suspend fun getUsers(): List<User> {
localDataSource.getCachedUsers()?.let { return it }
val users = remoteDataSource.fetchUsers()
localDataSource.cacheUsers(users)
return users
}
}
class UserRepository {
private let remote: UserRemoteDataSource
private let local: UserLocalDataSource
func getUsers() async throws -> [User] {
if let cached = await local.getCached() { return cached }
let users = try await remote.fetch()
await local.save(users)
return users
}
}
LRU Cache (Least Recently Used) è un algoritmo di caching in cui, quando viene raggiunto il limite, viene rimosso l'elemento a cui non si è acceduto da più tempo. Nelle applicazioni mobili, LRU Cache viene utilizzato per immagini, risposte API e oggetti serializzati. Un corretto caching dei dati riduce il numero di richieste di rete e accelera il caricamento dei contenuti. La cache nelle applicazioni mobili è un componente essenziale per alte prestazioni.
Memory Cache memorizza i dati nella RAM — l'accesso è estremamente veloce, ma la capacità è limitata dalla dimensione dell'heap dell'applicazione. Disk Cache salva le informazioni sul file system — più lento ma può contenere di più e persiste tra le sessioni. La strategia ottimale nello sviluppo mobile è una cache a due livelli: Memory Cache per i dati caldi e Disk Cache per i dati freddi. Quando si gestiscono i dati, viene prima controllata la cache di primo livello in memoria, poi la cache di secondo livello su disco.
class MemoryCache<K, V>(
private val maxSize: Int = 100
) {
private val cache = LinkedHashMap<K, V>(0, 0.75f, true)
fun get(key: K): V? = cache[key]
fun put(key: K, value: V) {
if (cache.size >= maxSize) {
cache.remove(cache.keys.first())
}
cache[key] = value
}
}
La cache TTL (Time To Live) rimuove automaticamente una voce dopo un intervallo di tempo specificato — adatta per dati API. Invalidazione basata su eventi pulisce la cache alla ricezione di una notifica push sulle modifiche. Nelle applicazioni mobili, la scelta della strategia di caching dipende dal tipo di dati: le immagini vengono memorizzate a lungo, mentre un feed di notizie richiede un'invalidazione frequente. Coil su Android e Kingfisher su iOS hanno già integrato LRU Cache per lavorare con le immagini.
Offline Queue è una struttura dati che memorizza le operazioni dell'utente (creazione, aggiornamento, eliminazione) in un database locale quando il dispositivo è offline. Quando la connessione viene ripristinata, Sync Manager applica sequenzialmente queste operazioni al server. Questo tipo di sincronizzazione dei dati garantisce che nessuna modifica venga persa durante una temporanea perdita di rete. Nello sviluppo mobile, Offline Queue è un componente critico per applicazioni con connessioni instabili.
La coda è costruita su una tabella in Room o SwiftData con campi: tipo di operazione, corpo della richiesta JSON, timestamp e stato. Sync Manager è un servizio in background che elabora le operazioni in sospeso, le invia al server, aggiorna lo stato e rimuove le voci completate con successo. La sincronizzazione dei dati tramite WorkManager su Android o BGTaskScheduler su iOS continua anche dopo il riavvio del dispositivo. L'uso di Offline Queue insieme a una corretta gestione dei dati garantisce un'esperienza utente fluida.
@Entity
data class SyncOperation(
@PrimaryKey val id: Long,
val endpoint: String,
val method: String,
val body: String,
val createdAt: Long
)
class SyncManager(
private val dao: SyncOperationDao,
private val api: ApiService
) {
suspend fun syncPending() {
dao.getPendingOperations().forEach { op ->
try {
api.execute(op.endpoint, op.method, op.body)
dao.delete(op.id)
} catch (e: Exception) {
// retry on next cycle
}
}
}
}
Backoff esponenziale tra i tentativi (1s, 2s, 4s, 8s) protegge il server da sovraccarichi improvvisi e previene tentativi infiniti. Il limite di 5 tentativi impedisce il traboccamento della coda. La sincronizzazione dei dati nelle applicazioni mobili con supporto all'idempotenza lato server consente tentativi sicuri, evitando duplicati. Ciò è particolarmente importante per transazioni finanziarie e ordini.
Conflict Resolution è un insieme di strategie per situazioni in cui gli stessi dati vengono modificati su dispositivi diversi contemporaneamente. La sincronizzazione di base dei dati richiede la scelta di un approccio: Last-Write-Wins (vince l'ultima scrittura), versionamento (vince la versione più alta) o risoluzione manuale. In scenari complessi, vengono utilizzati CRDT (Conflict-Free Replicated Data Types), che garantiscono la convergenza matematica dei dati.
Last-Write-Wins è la più semplice da implementare ma può perdere le modifiche dell'utente. Version Vector — ogni record memorizza un numero di versione e un identificatore del dispositivo; un conflitto sorge quando le versioni non corrispondono. CRDT è la strategia più affidabile ma complessa: i dati convergono matematicamente a uno stato unico senza un coordinatore centralizzato. La sincronizzazione dei dati nelle applicazioni mobili basata su CRDT viene utilizzata nella modifica collaborativa in Google Docs e nella sincronizzazione delle note in Notion.
Quando un'applicazione viene aggiornata, la struttura del database locale cambia: vengono aggiunte colonne, tabelle, indici. Schema Migration è il processo di trasformazione di un database esistente in un nuovo schema senza perdita di dati. Room supporta le migrazioni tramite la classe Migration con versioni vecchia e nuova. SwiftData utilizza VersionedSchema per descrivere le modifiche. La corretta sincronizzazione dei dati tra le versioni dell'applicazione richiede che le migrazioni vengano testate in modo idempotente.
val migration1to2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE users ADD COLUMN avatar_url TEXT")
}
}
@Database(
entities = [User::class],
version = 2
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
enum ConflictStrategy {
case lastWriteWins
case versionVector
case crdt
}
struct VersionedDocument {
let id: String
let version: Int
let data: Data
let editedBy: String
func resolve(with remote: VersionedDocument) -> VersionedDocument {
return version >= remote.version ? self : remote
}
}
Room è una libreria Google per l'archiviazione locale su Android, costruita su SQLite e che fornisce annotazioni per descrizioni dichiarative delle query. SwiftData è un framework Apple per iOS, macOS, watchOS e visionOS, successore di Core Data con una sintassi concisa di Swift Macro. Entrambi gli strumenti risolvono il compito di gestire i dati sul dispositivo, ma con approcci diversi all'organizzazione del codice. La cache nelle applicazioni mobili è spesso costruita proprio su queste tecnologie.
Room utilizza annotazioni @Entity per le tabelle e @Dao per le query. DAO incapsula tutte le operazioni SQL con verifica in fase di compilazione — gli errori di sintassi SQL vengono rilevati prima dell'esecuzione. Type Converter converte i tipi complessi (Date, List) in primitivi SQLite. La moderna gestione dei dati nelle applicazioni Android è costruita attorno a Room + Flow, fornendo aggiornamenti reattivi dell'interfaccia quando la cache o il database locale cambia.
SwiftData utilizza la macro @Model per definire le entità e @Query per osservare i dati. Il framework traccia automaticamente le dipendenze e aggiorna l'interfaccia ai cambiamenti. La migrazione dello schema utilizza VersionedSchema che descrive tutte le versioni. La sincronizzazione dei dati tra SwiftData e il server è implementata tramite un Sync Manager personalizzato che si iscrive agli aggiornamenti via @Query.
@Model
final class UserModel {
var id: String
var name: String
var email: String
var updatedAt: Date
init(id: String, name: String, email: String) {
self.id = id
self.name = name
self.email = email
self.updatedAt = Date()
}
}
| Criterio | Room | SwiftData |
|---|---|---|
| Piattaforma | Android | Apple (iOS, macOS, visionOS) |
| Base | SQLite | SQLite (stack Core Data) |
| Sintassi | Annotazioni Kotlin | Swift Macro |
| Migrazioni | Classe Migration | VersionedSchema |
| Reattività | Flow / LiveData | Property wrapper @Query |
| Multipiattaforma | Solo Android | Solo Apple |
Domande Frequenti
LRU Cache è un algoritmo di caching che, quando viene raggiunto il limite, rimuove l'elemento meno recentemente utilizzato. Viene utilizzato per immagini e dati API nelle applicazioni mobili.
Offline Queue salva le operazioni dell'utente in un database locale quando non c'è rete. Sync Manager le esegue quando la connessione viene ripristinata, garantendo che le modifiche vengano consegnate al server.
Conflict Resolution è una strategia per risolvere i conflitti durante la sincronizzazione dei dati. Approcci principali: Last-Write-Wins, Version Vector e CRDT per sistemi distribuiti.
Per Android scegliete Room — una libreria matura con verifica SQL in fase di compilazione. Per iOS — SwiftData con sintassi dichiarativa. Per progetti multipiattaforma, SQLDelight o Realm sarebbero adatti.
La sincronizzazione dei dati ottimale è ad ogni modifica per le operazioni critiche e in background ogni 15–30 minuti per le altre. Utilizzate le notifiche push per la consegna istantanea.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.