Skip to content

[Flutter] 使用 sqflite 套件操作 SQLite 儲存資料

Last Updated on 2021-10-13 by Clay

在我們使用 Flutter 開發 APP 時,若有大量『儲存』資料的需求,則通常會使用 SQLite 這類嵌入式的資料庫來儲存資料。今天我要紀錄的,便是在 Flutter 中,如何透過 sqflite 套件來進行 SQLite 嵌入式資料庫的建立、讀取、插入(insert)、刪除(delete),更新(update)資料等操作。


事前準備

要使用 sqflite 套件,必須在 pubspec.yaml 檔案中加入以下套件名稱(或許還加上指定版本):

需要使用的套件分別為 sqflite 以及 path。前者是讓我們操作 SQLite 資料庫的套件、後者是讓我們在不同平台上都能準確讀取指定路徑的套件。

寫下之後,可以再透過 IDE 的輔助或是以下指令:

flutter pub get


來取得套件。


使用 sqflite 建立資料庫

在開始之前,我先說明我的範例程式。我的資料庫目標是要儲存『超級英雄』(SuperHero)的資料,為此我需要 3 份不同的檔案:

lib/
├── DB.dart
├── hero.dart
└── main.dart

  • DB.dart: 資料庫的所有操作都寫在這份檔案中
  • hero.dart: 超級英雄模板的 Class 撰寫於此
  • main.dart: 範例用的程式碼,插入蝙蝠俠、超人等資料,並刪除蝙蝠俠的資料


hero.dart

如上所述,這是超級英雄的模板類別程式。

class SuperHero {
  // Init
  final int id;
  final String name;
  final int age;
  final String ability;
  SuperHero({this.id, this.name, this.age, this.ability,});

  // toMap()
  Map<String, dynamic> toMap() {
    return {
      "id": id,
      "name": name,
      "age": age,
      "ability": ability,
    };
  }

  @override
  String toString() {
    return "SuperHero{\n  id: $id\n  name: $name\n  age: $age\n  ability: $ability\n}\n\n";
  }
}


在這份程式碼中,我定義 SuperHero 這一類別。其中包含著:

  • 編號(id)
  • 名字(name)
  • 年齡(age)
  • 能力(ability)

等不同的項目。

除此之外,則依照官方教程重寫了 toString() 函式,這讓我們能選擇這個類別印出後的格式。


DB.dart

這份程式碼就是資料庫的基本操作了。

import 'dart:async';

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

import 'package:flutter_app/hero.dart';


class HeroDB {
  static Database database;

  // Initialize database
  static Future<Database> initDatabase() async {
    database = await openDatabase(
      // Ensure the path is correctly for any platform
      join(await getDatabasesPath(), "hero_database.db"),
      onCreate: (db, version) {
        return db.execute(
          "CREATE TABLE HEROS("
              "id INTEGER PRIMARY KEY,"
              "name TEXT,"
              "age INTEGER,"
              "ability TEXT"
              ")"
        );
      },

      // Version
      version: 1,
    );

    return database;
  }

  // Check database connected
  static Future<Database> getDatabaseConnect() async {
    if (database != null) {
      return database;
    }
    else {
      return await initDatabase();
    }
  }

  // Show all data
  static Future<List<SuperHero>> showAllData() async {
    final Database db = await getDatabaseConnect();
    final List<Map<String, dynamic>> maps = await db.query("HEROS");

    return List.generate(maps.length, (i) {
      return SuperHero(
        id: maps[i]["id"],
        name: maps[i]["name"],
        age: maps[i]["age"],
        ability: maps[i]["ability"],
      );
    });
  }

  // Insert
  static Future<void> insertData(SuperHero hero) async {
    final Database db = await getDatabaseConnect();
    await db.insert(
      "HEROS",
      hero.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  // Update
  static Future<void> updateData(SuperHero hero) async {
    final db = await getDatabaseConnect();
    await db.update(
      "HEROS",
      hero.toMap(),
      where: "id = ?",
      whereArgs: [hero.id],
    );
  }

  // Delete
  static Future<void> deleteData(int id) async {
    final db = await getDatabaseConnect();
    await db.delete(
      "HEROS",
      where: "id = ?",
      whereArgs: [id],
    );
  }
}


乍看之下會覺得很長、很多、很麻煩 —— 實際上定睛一看,會發現其實只有 5 個重要的函式:

  • initDatabase() => 後又被 getDatabaseConnect() 包裝起來。就是建立最基本的函式庫的程式
  • showAllData(): 沒有查詢,直接回傳資料庫中的所有資料
  • insertData(): 插入一筆資料
  • updateData(): 修改一筆資料
  • deleteData(): 刪除一筆資料

其實沒那麼複雜,對吧?


main.dart

那麼,接下來就是最後測試的程式了。別忘了把 hero.dart 以及 DB.dart 都匯入。匯入時不論相對路徑還是絕對路徑都是可以的。

比方說我的專案名稱是 flutter_app(這是預設名稱),那麼我想要匯入 DB.dart,則在程式碼開頭寫下:

import 'package:flutter_app/DB.dart';


即可。

那麼,以下是完整的程式碼,可以看到我匯入了蝙蝠俠、超人等資料;緊接著又刪除了蝙蝠俠的資料。然後我總共印出了兩次。

import 'package:flutter/widgets.dart';

import 'package:flutter_app/hero.dart';
import 'package:flutter_app/DB.dart';


void main() async {
  // Avoid errors
  WidgetsFlutterBinding.ensureInitialized();

  // Open the database
  //final Future<Database> database = HeroDB.getDatabaseConnect();

  // Main work
  // Batman
  var batman = SuperHero(
    id: 0,
    name: "Batman",
    age: 50,
    ability: "Rich",
  );

  // Superman
  var superman = SuperHero(
    id: 1,
    name: "Superman",
    age: 35,
    ability: "I can fly",
  );

  // Main work
  await HeroDB.insertData(batman);
  await HeroDB.insertData(superman);
  print(await HeroDB.showAllData());

  await HeroDB.deleteData(0);
  print(await HeroDB.showAllData());

}


Output:

I/flutter ( 9807): [SuperHero{
I/flutter ( 9807):   id: 0
I/flutter ( 9807):   name: Batman
I/flutter ( 9807):   age: 50
I/flutter ( 9807):   ability: Rich
I/flutter ( 9807): }
I/flutter ( 9807): 
I/flutter ( 9807): , SuperHero{
I/flutter ( 9807):   id: 1
I/flutter ( 9807):   name: Superman
I/flutter ( 9807):   age: 35
I/flutter ( 9807):   ability: I can fly
I/flutter ( 9807): }
I/flutter ( 9807): 
I/flutter ( 9807): ]
I/flutter ( 9807): [SuperHero{
I/flutter ( 9807):   id: 1
I/flutter ( 9807):   name: Superman
I/flutter ( 9807):   age: 35
I/flutter ( 9807):   ability: I can fly
I/flutter ( 9807): }
I/flutter ( 9807): 
I/flutter ( 9807): ]


於是你會看到,在第一次印出時,蝙蝠俠和超人都在資料庫中;到第二次印出時,被刪除的蝙蝠俠已經不見了,只留下超人的資料。

順帶一提其實我比較喜歡蝙蝠俠,蝙蝠俠突然消失並不是在暗喻什麼,謝謝大家。


References


Read More

Leave a Reply