如果您正在開發需要持久化和查詢大量本地裝置資料的應用,請考慮使用資料庫而不是本地檔案或鍵值儲存。通常,與其它本地持久化解決方案相比,資料庫在插入、更新和查詢方面速度更快。

Flutter 應用可以透過 pub.dev 上提供的 sqflite 外掛來使用 SQLite 資料庫。本示例演示瞭如何使用 sqflite 來插入、讀取、更新和刪除各種 Dog 的資料。

如果您不熟悉 SQLite 和 SQL 語句,請在完成本示例之前,先檢視 SQLite 教程 以瞭解基礎知識。

本示例將採取以下步驟

  1. 新增依賴項。
  2. 定義 Dog 資料模型。
  3. 開啟資料庫。
  4. 建立 dogs 表。
  5. 向資料庫中插入一條 Dog 記錄。
  6. 檢索 Dog 列表。
  7. 更新資料庫中的 Dog 記錄。
  8. 從資料庫中刪除 Dog 記錄。

1. 新增依賴項

#

要使用 SQLite 資料庫,請匯入 sqflitepath 程式包。

  • sqflite 程式包提供了與 SQLite 資料庫互動的類和函式。
  • path 程式包提供了用於定義資料庫在磁碟上的儲存位置的函式。

要將程式包新增為依賴項,請執行 flutter pub add

flutter pub add sqflite path

確保在您將要使用的檔案中匯入這些程式包。

dart
import 'dart:async';

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

2. 定義 Dog 資料模型

#

在建立用於儲存 Dog 資訊的表之前,花點時間定義需要儲存的資料。在此示例中,請定義一個 Dog 類,其中包含三項資料:每個 Dog 的唯一 idnameage

dart
class Dog {
  final int id;
  final String name;
  final int age;

  const Dog({required this.id, required this.name, required this.age});
}

3. 開啟資料庫

#

在讀取和寫入資料庫資料之前,請開啟到資料庫的連線。這涉及兩個步驟:

  1. 使用 sqflite 程式包中的 getDatabasesPath() 函式,結合 path 程式包中的 join 函式,來定義資料庫檔案的路徑。
  2. 使用 sqflite 中的 openDatabase() 函式開啟資料庫。
dart
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final database = openDatabase(
  // Set the path to the database. Note: Using the `join` function from the
  // `path` package is best practice to ensure the path is correctly
  // constructed for each platform.
  join(await getDatabasesPath(), 'doggie_database.db'),
);

4. 建立 dogs

#

接下來,建立一個表來儲存各種 Dog 的資訊。在此示例中,建立一個名為 dogs 的表,該表定義了可以儲存的資料。每個 Dog 包含一個 idnameage。因此,這些在 dogs 表中表示為三個列。

  1. id 是 Dart 的 int 型別,並存儲為 INTEGER SQLite 資料型別。使用 id 作為表的主鍵也是一個好習慣,這可以提高查詢和更新的速度。
  2. name 是 Dart 的 String 型別,並存儲為 TEXT SQLite 資料型別。
  3. age 也是 Dart 的 int 型別,並存儲為 INTEGER 資料型別。

有關可以在 SQLite 資料庫中儲存的可用資料型別的更多資訊,請參閱 SQLite 官方資料型別文件

dart
final database = openDatabase(
  // Set the path to the database. Note: Using the `join` function from the
  // `path` package is best practice to ensure the path is correctly
  // constructed for each platform.
  join(await getDatabasesPath(), 'doggie_database.db'),
  // When the database is first created, create a table to store dogs.
  onCreate: (db, version) {
    // Run the CREATE TABLE statement on the database.
    return db.execute(
      'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
    );
  },
  // Set the version. This executes the onCreate function and provides a
  // path to perform database upgrades and downgrades.
  version: 1,
);

5. 向資料庫中插入一條 Dog 記錄

#

現在您已經擁有了一個包含適合儲存各種 Dog 資訊的表的資料庫,是時候進行讀寫資料了。

首先,向 dogs 表插入一條 Dog 記錄。這涉及兩個步驟:

  1. Dog 轉換為 Map
  2. 使用 insert() 方法將 Map 儲存到 dogs 表中。
dart
class Dog {
  final int id;
  final String name;
  final int age;

  Dog({required this.id, required this.name, required this.age});

  // Convert a Dog into a Map. The keys must correspond to the names of the
  // columns in the database.
  Map<String, Object?> toMap() {
    return {'id': id, 'name': name, 'age': age};
  }

  // Implement toString to make it easier to see information about
  // each dog when using the print statement.
  @override
  String toString() {
    return 'Dog{id: $id, name: $name, age: $age}';
  }
}
dart
// Define a function that inserts dogs into the database
Future<void> insertDog(Dog dog) async {
  // Get a reference to the database.
  final db = await database;

  // Insert the Dog into the correct table. You might also specify the
  // `conflictAlgorithm` to use in case the same dog is inserted twice.
  //
  // In this case, replace any previous data.
  await db.insert(
    'dogs',
    dog.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}
dart
// Create a Dog and add it to the dogs table
var fido = Dog(id: 0, name: 'Fido', age: 35);

await insertDog(fido);

6. 檢索 Dog 列表

#

現在 Dog 已儲存在資料庫中,可以查詢資料庫以獲取特定 Dog 或所有 Dog 的列表。這涉及兩個步驟:

  1. dogs 表執行 query。這將返回一個 List<Map>
  2. List<Map> 轉換為 List<Dog>
dart
// A method that retrieves all the dogs from the dogs table.
Future<List<Dog>> dogs() async {
  // Get a reference to the database.
  final db = await database;

  // Query the table for all the dogs.
  final List<Map<String, Object?>> dogMaps = await db.query('dogs');

  // Convert the list of each dog's fields into a list of `Dog` objects.
  return [
    for (final {'id': id as int, 'name': name as String, 'age': age as int}
        in dogMaps)
      Dog(id: id, name: name, age: age),
  ];
}
dart
// Now, use the method above to retrieve all the dogs.
print(await dogs()); // Prints a list that include Fido.

7. 更新資料庫中的 Dog 記錄

#

在向資料庫插入資訊後,您可能希望稍後更新該資訊。您可以透過使用 sqflite 庫中的 update() 方法來做到這一點。

這涉及兩個步驟:

  1. 將 Dog 轉換為 Map。
  2. 使用 where 子句來確保更新正確的 Dog。
dart
Future<void> updateDog(Dog dog) async {
  // Get a reference to the database.
  final db = await database;

  // Update the given Dog.
  await db.update(
    'dogs',
    dog.toMap(),
    // Ensure that the Dog has a matching id.
    where: 'id = ?',
    // Pass the Dog's id as a whereArg to prevent SQL injection.
    whereArgs: [dog.id],
  );
}
dart
// Update Fido's age and save it to the database.
fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
await updateDog(fido);

// Print the updated results.
print(await dogs()); // Prints Fido with age 42.

8. 從資料庫中刪除 Dog 記錄

#

除了插入和更新 Dog 的資訊外,您還可以從資料庫中刪除 Dog。要刪除資料,請使用 sqflite 庫中的 delete() 方法。

在此部分,建立一個函式,該函式接受一個 id 並從資料庫中刪除具有匹配 id 的 Dog。要使其正常工作,您必須提供一個 where 子句來限制要刪除的記錄。

dart
Future<void> deleteDog(int id) async {
  // Get a reference to the database.
  final db = await database;

  // Remove the Dog from the database.
  await db.delete(
    'dogs',
    // Use a `where` clause to delete a specific dog.
    where: 'id = ?',
    // Pass the Dog's id as a whereArg to prevent SQL injection.
    whereArgs: [id],
  );
}

示例

#

執行示例:

  1. 建立一個新的 Flutter 專案。
  2. sqflitepath 程式包新增到您的 pubspec.yaml 檔案中。
  3. 將以下程式碼貼上到一個名為 lib/db_test.dart 的新檔案中。
  4. 使用 flutter run lib/db_test.dart 執行程式碼。
dart
import 'dart:async';

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

void main() async {
  // Avoid errors caused by flutter upgrade.
  // Importing 'package:flutter/widgets.dart' is required.
  WidgetsFlutterBinding.ensureInitialized();
  // Open the database and store the reference.
  final database = openDatabase(
    // Set the path to the database. Note: Using the `join` function from the
    // `path` package is best practice to ensure the path is correctly
    // constructed for each platform.
    join(await getDatabasesPath(), 'doggie_database.db'),
    // When the database is first created, create a table to store dogs.
    onCreate: (db, version) {
      // Run the CREATE TABLE statement on the database.
      return db.execute(
        'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
      );
    },
    // Set the version. This executes the onCreate function and provides a
    // path to perform database upgrades and downgrades.
    version: 1,
  );

  // Define a function that inserts dogs into the database
  Future<void> insertDog(Dog dog) async {
    // Get a reference to the database.
    final db = await database;

    // Insert the Dog into the correct table. You might also specify the
    // `conflictAlgorithm` to use in case the same dog is inserted twice.
    //
    // In this case, replace any previous data.
    await db.insert(
      'dogs',
      dog.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  // A method that retrieves all the dogs from the dogs table.
  Future<List<Dog>> dogs() async {
    // Get a reference to the database.
    final db = await database;

    // Query the table for all the dogs.
    final List<Map<String, Object?>> dogMaps = await db.query('dogs');

    // Convert the list of each dog's fields into a list of `Dog` objects.
    return [
      for (final {'id': id as int, 'name': name as String, 'age': age as int}
          in dogMaps)
        Dog(id: id, name: name, age: age),
    ];
  }

  Future<void> updateDog(Dog dog) async {
    // Get a reference to the database.
    final db = await database;

    // Update the given Dog.
    await db.update(
      'dogs',
      dog.toMap(),
      // Ensure that the Dog has a matching id.
      where: 'id = ?',
      // Pass the Dog's id as a whereArg to prevent SQL injection.
      whereArgs: [dog.id],
    );
  }

  Future<void> deleteDog(int id) async {
    // Get a reference to the database.
    final db = await database;

    // Remove the Dog from the database.
    await db.delete(
      'dogs',
      // Use a `where` clause to delete a specific dog.
      where: 'id = ?',
      // Pass the Dog's id as a whereArg to prevent SQL injection.
      whereArgs: [id],
    );
  }

  // Create a Dog and add it to the dogs table
  var fido = Dog(id: 0, name: 'Fido', age: 35);

  await insertDog(fido);

  // Now, use the method above to retrieve all the dogs.
  print(await dogs()); // Prints a list that include Fido.

  // Update Fido's age and save it to the database.
  fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
  await updateDog(fido);

  // Print the updated results.
  print(await dogs()); // Prints Fido with age 42.

  // Delete Fido from the database.
  await deleteDog(fido.id);

  // Print the list of dogs (empty).
  print(await dogs());
}

class Dog {
  final int id;
  final String name;
  final int age;

  Dog({required this.id, required this.name, required this.age});

  // Convert a Dog into a Map. The keys must correspond to the names of the
  // columns in the database.
  Map<String, Object?> toMap() {
    return {'id': id, 'name': name, 'age': age};
  }

  // Implement toString to make it easier to see information about
  // each dog when using the print statement.
  @override
  String toString() {
    return 'Dog{id: $id, name: $name, age: $age}';
  }
}