Cos'è Isar
Quando si parla di persistenza locale in Flutter, spesso si pensa subito a SQLite o a soluzioni relazionali. Ma esiste un'alternativa NoSQL nata specificamente per Flutter e Dart: Isar. È un database embedded estremamente veloce, con supporto a query tipizzate, indici composti, transazioni e — cosa fondamentale — la reattività tramite watch.
A differenza di soluzioni come Hive, Isar è pensato anche per dataset di grandi dimensioni, offre query complesse e un ottimo supporto agli indici, mantenendo comunque un'API pulita e generata automaticamente.
Perché sceglierlo
- Performance: scritto in Rust, con benchmark che lo pongono tra i più rapidi.
- Type-safe: le query sono generate in base ai modelli, con autocompletamento.
- Reattivo: puoi ascoltare i cambiamenti su collezioni e singoli oggetti.
- Cross-platform: iOS, Android, desktop e web.
- Zero configurazione SQL: nessuna gestione manuale di schemi o migrazioni complesse.
Installazione
Aggiungi le dipendenze al pubspec.yaml:
dependencies:
isar: ^3.1.0
isar_flutter_libs: ^3.1.0
path_provider: ^2.1.0
dev_dependencies:
isar_generator: ^3.1.0
build_runner: ^2.4.0
Definire un modello
Le collezioni si dichiarano con l'annotazione @collection. Ogni oggetto ha bisogno di un campo id di tipo Id.
import 'package:isar/isar.dart';
part 'ricetta.g.dart';
@collection
class Ricetta {
Id id = Isar.autoIncrement;
@Index(caseSensitive: false)
late String titolo;
late int minutiPreparazione;
@Index()
late List<String> tag;
bool preferita = false;
}
Dopo aver definito i modelli, genera il codice:
dart run build_runner build
Questo crea il file ricetta.g.dart con schema e query tipizzate.
Aprire il database
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
Future<Isar> apriDatabase() async {
final dir = await getApplicationDocumentsDirectory();
return Isar.open(
[RicettaSchema],
directory: dir.path,
);
}
Conviene mantenere una singola istanza di Isar per tutta la durata dell'app, ad esempio tramite un service o l'iniezione delle dipendenze.
Scrivere dati
Tutte le operazioni di scrittura avvengono all'interno di una transazione:
final ricetta = Ricetta()
..titolo = 'Pasta al pomodoro'
..minutiPreparazione = 20
..tag = ['primo', 'veloce'];
await isar.writeTxn(() async {
await isar.ricettas.put(ricetta); // insert o update
});
Il metodo put gestisce sia l'inserimento sia l'aggiornamento in base all'id.
Leggere e interrogare
Le query sono fluide e tipizzate:
// Tutte le ricette
final tutte = await isar.ricettas.where().findAll();
// Filtri con condizioni
final veloci = await isar.ricettas
.filter()
.minutiPreparazioneLessThan(30)
.and()
.preferitaEqualTo(true)
.findAll();
// Ricerca per tag e ordinamento
final primi = await isar.ricettas
.filter()
.tagElementEqualTo('primo')
.sortByTitolo()
.findAll();
Reattività con watch
Uno dei punti di forza di Isar è la possibilità di reagire ai cambiamenti. Perfetto da combinare con StreamBuilder o con soluzioni di state management.
Stream<List<Ricetta>> osservaPreferite() {
return isar.ricettas
.filter()
.preferitaEqualTo(true)
.watch(fireImmediately: true);
}
Nell'interfaccia:
StreamBuilder<List<Ricetta>>(
stream: repository.osservaPreferite(),
builder: (context, snapshot) {
final ricette = snapshot.data ?? [];
return ListView.builder(
itemCount: ricette.length,
itemBuilder: (_, i) => ListTile(
title: Text(ricette[i].titolo),
),
);
},
)
Con fireImmediately: true lo stream emette subito lo stato attuale, poi ogni volta che i dati cambiano.
Eliminare dati
await isar.writeTxn(() async {
await isar.ricettas.delete(id);
// oppure con query
await isar.ricettas.filter().preferitaEqualTo(false).deleteAll();
});
Buone pratiche
- Isola l'accesso ai dati in un repository, evitando di chiamare Isar direttamente dai widget.
- Usa gli indici sui campi su cui filtri o ordini spesso: migliorano drasticamente le performance.
- Riutilizza l'istanza di Isar invece di aprirla più volte.
- Sfrutta i link (
IsarLinkeIsarLinks) per modellare relazioni tra collezioni senza chiavi esterne manuali. - Per progetti nuovi valuta anche lo stato del maintenance: verifica sempre la versione più recente sul repository ufficiale.
Conclusione
Isar è una scelta eccellente quando serve una persistenza locale veloce, reattiva e priva della complessità di SQL. La combinazione tra query tipizzate, indici e stream lo rende particolarmente adatto ad app che devono aggiornare l'interfaccia in tempo reale al variare dei dati. Integrandolo dietro un repository ottieni un layer dati pulito, testabile e facilmente sostituibile.
