Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 35 di 37
  • livello intermedio
Indice lezioni

SQFLite: come gestire un DB SQLite con Flutter

Impariamo ad usare il plugin SQFLite per gestire una database all'interno di un'applicazione mobile multipiattaforma realizzata tramite Flutter.
Impariamo ad usare il plugin SQFLite per gestire una database all'interno di un'applicazione mobile multipiattaforma realizzata tramite Flutter.
Link copiato negli appunti

Nelle precedenti lezioni abbiamo visto come scrivere file di testo per salvare informazioni all’interno della documents directory di un’app o come salvare dei dati primitivi quali stringhe, interi e booleani tramite le shared preferences.

In questa lezione, vedremo invece come creare e gestire un DB SQLite (di cui abbiamo già parlato qui su HTML.it), uno degli aspetti più importanti durante lo sviluppo di un’app mobile.

I plugin sqflite e path

Per poter lavorare con un DB SQLite, la community di Flutter mette a disposizione degli sviluppatori il plugin sqflite. Il plugin supporta ovviamente sia iOS che Android e offre diverse funzionalità come:

  • supporto per le transazioni e operazioni batch;
  • gestione automatica della versione durante l'apertura
  • helper per la definizione di classiche operazioni CRUD;
  • le operazioni di comunicazione col DB sono eseguite in un thread in background sia su iOS sia su Android.

Oltre al plugin sqflite, è necessario utilizzare anche il plugin path, fondamentale per le funzioni offerte per salvare il database SQLite su disco.

Esempio pratico

Come sempre, creiamo un nuovo progetto come descritto nella lezione 6.

Procediamo adesso step by step per creare un’applicazione che ci permetta di gestire un DB SQLite. In particolare, vedremo un semplice esempio per aggiungere, rimuovere, cancellare e selezionare uno o più articoli dal nostro database.

La struttura del nostro progetto sarà quindi la seguente

lib/
│── screens/
│   │── screen1
│   │      │── screen1.dart
│── theme/
│   │── style.dart
│── models/
│   │── article.dart
│── repositories/
│   │── dbhelper.dart
│   │── article_repository.dart
│── routes.dart

In questa lezione, non vedremo i dettagli implementativi dell’interfaccia utente, lasciando al lettore la libertà di implementare una propria soluzione o di prendere spunto da quella offerta nel progetto rilasciato su GitHub.

Installazione dei plugin

Apriamo il file pubspec.yaml e inseriamo sotto la voce dependencies i nomi dei plugin di interesse come segue

dependencies:
  # . . .
  sqflite: ^1.3.0
  path:

Eseguiamo dal nostro terminale il comando

flutter pub get

per installare il plugin.

Definizione della classe Article

Definiamo la classe Article che ci permetterà di descrivere alcune semplici caratteristiche di un articolo che sono:

  • il titolo;
  • l’autore;
  • un id univoco che lo identifichi.

Una semplice implementazione può essere ad esempio la seguente.

class Article {
  @required
  final int id;
  @required
  String title;
  @required
  String author;
  Article(this.id, this.title, this.author);
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'author': author,
    };
  }
  Article.fromMapObject(Map<String, dynamic> authorMap)
      : id = authorMap['id'],
        title = authorMap['title'],
        author = authorMap['author'];
  @override
  String toString() {
    return 'Article{id: $id, title: $title, author: $author}';
  }
}

Oltre alla definizione della classe abbiamo aggiunto altri tre metodi di utilità:

Metodo Descrizione
toMap permette di trasformare l’oggetto corrente in una Map
fromMapToObject permette di trasformare una Map composta dalle caratteristiche della classe in un oggetto di quella classe
toString semplice override del metodo toString per ottenere facilmente una descrizione dell’oggetto

Definizione dello strato repository

Definiamo ora le classi necessarie per la creazione del nostro database e per interagire con questo.

Partiamo dalla creazione e gestione del database e definiamo il file dbhelper.dart che conterrà:

  • una variabile db di tipo Database offerta da sqflite che rappresenta il database da interrogare;
  • la classe DBHelper.

Lo scopo di questa classe è quello di:

  • definire la struttura del database creando le tabelle di interesse;
  • esporre un metodo per inizializzare il db.

Per fare ciò dobbiamo creare i seguenti metodi asincroni

Metodo Descrizione
createArticleTable responsabile della creazione della tabella Article tramite semplice query CREATE di SQLite
getDatabasePath definisce il percorso al file rappresentante il DB recuperato tramite il metodo getDatabasesPath di sqflite combinato tramite join con il nome desiderato per il database. Se non esistono, crea il file e le relative sottocartelle
initDatabase esegue l’inizializzazione del db invocando il metodo openDatabase di sqflite e creando la tabella di interesse tramite la callback onCreate. Questo metodo è importante in quanto permette anche di effettuare gli aggiornamenti di versione

Si riporta di seguito una semplice implementazione della classe DBHelper:

Database db;
class DBHelper {
  static const dbName = 'htmlit';
  static const articlesTable = 'articles';
  static const id = 'id';
  static const title = 'title';
  static const author = 'author';
  Future<void> createArticleTable(Database db) async {
    final todoSql = '''CREATE TABLE $articlesTable (
      $id INTEGER PRIMARY KEY,
      $title TEXT,
      $author TEXT)''';
    await db.execute(todoSql);
  }
  Future<String> getDatabasePath(String dbName) async {
    final databasePath = await getDatabasesPath();
    final path = join(databasePath, dbName);
    if (!await Directory(dirname(path)).exists()) {
      await Directory(dirname(path)).create(recursive: true);
    }
    return path;
  }
  Future<void> initDatabase() async {
    final path = await getDatabasePath(dbName);
    db = await openDatabase(path, version: 1, onCreate: onCreate);
    print(db);
  }
  Future<void> onCreate(Database db, int version) async {
    await createArticleTable(db);
  }
}

Passiamo adesso alla definizione dello strato di repository e di interrogazione dei nostri dati e creiamo il file article_repository.dart che conterrà l’omonima classe ArticleRepository.

Vediamo un semplice esempio di come si può inserire un nuovo oggetto nel DB tramite una query di INSERT.

class ArticleRepository {
  static Future<void> addArticle(Article article) async {
    final sql = '''INSERT INTO ${DBHelper.articlesTable}(
                    ${DBHelper.id},
                    ${DBHelper.title},
                    ${DBHelper.author})
                  VALUES (?,?,?)
                ''';
    List<dynamic> params = [article.id, article.title, article.author];
    await db.rawInsert(sql, params);
  }
}

In questo caso abbiamo:

  • assato al nostro metodo asincrono un oggetto Article;
  • definito la query SQL da eseguire;
  • definita la lista di parametri nell’ordine desiderato dalla query SQL;
  • invocato il metodo rawInsert passando come parametro la query sql e i parametri.

Il metodo rawInsert si occuperà di costruire la query ed eseguirla.

Questo approccio è però eccessivamente verboso e può essere facilmente sostituito da una versione più snella offerta dal metodo insert di sqflite. Vediamo come cambia la precedente implementazione.

static Future<void> addArticle(Article article) async {
    await db.insert(DBHelper.articlesTable, article.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

In questo caso il metodo ha richiesto come input:

  • la tabella su cui eseguire la query;
  • le informazioni da passare in formato chiave-valore e ottenibili tramite il metodo Article.toMap;
  • l’algoritmo da utilizzare in caso di conflitti.

Oltre al metodo insert, la libreria offre anche altri metodi quali delete, update e query per cancellare, aggiornare ed eseguire query come la selezione di tutti gli articoli presenti nella tabella.

Lasciamo quindi al lettore l’implementazione di questi metodi ricordando che potrà trovare una possibile implementazione nel codice rilasciato per questa lezione.

Inizializzazione del DB

Ora che abbiamo definito lo strato repository possiamo inizializzare finalmente il nostro DB all’avvio dell’app.

All’interno del file main.dart sostituiamo la definizione del metodo main() con la seguente implementazione.

void main() async {
  await DBHelper().initDatabase();
  runApp(MyApp());
}

In questo modo, all’avvio dell’applicazione il DB verrà inizializzato e potrà essere interrogato in modo semplice e diretto.

Immaginando di aver definito un’interfaccia composta da una form per creare nuovi articoli e da una lista che si aggiorna automaticamente ad ogni nuova interrogazione al DB, eseguiamo la nostra applicazione ottenendo il risultato in figura.

Figura 178b. Esempio di utilizzo di sqflite per iOS (click per ingrandire)Esempio di utilizzo di sqflite per iOS
Figura 178a. Esempio di utilizzo di sqflite per Android (click per ingrandire)Esempio di utilizzo di sqflite per Android

Come possiamo vedere, in poche e semplici righe di codice abbiamo integrato all’interno della nostra applicazione un DB SQLite per gestire le informazioni riguardanti un articolo.

Il codice di questa lezione con le relative soluzioni su come implementare i metodi CRUD e l’interfaccia utente sono disponibili su GitHub.

Ti consigliamo anche