用 SQLite 做資料持久化
如果你正在編寫一個需要持久化且查詢大量本地裝置資料的 app,可考慮採用資料庫,而不是本地資料夾或關鍵值函式庫。總的來說,相比於其他本地持久化方案來說,資料庫能夠提供更為迅速的插入、更新、查詢功能。
Flutter應用程式中可以透過 sqflite
package
來使用 SQLite 資料庫。本文將透過使用 sqflite
來示範插入,讀取,更新,刪除各種狗狗的資料。
如果你對於 SQLite 和 SQL 的各種語句還不熟悉,請檢視 SQLite 官方的教程 SQLite 教程,在檢視本文之前需要掌握基本的SQL語句。
總共有以下的步驟:
-
新增依賴;
-
定義
Dog (狗)
資料模型; -
開啟資料庫;
-
建立
dogs
資料表; -
將一條
Dog
資料插入資料庫; -
查詢所有狗狗的資料;
-
更新(修改)一條
Dog
的資料; -
刪除一條
Dog
的資料。
1. 新增依賴
為了使用 SQLite 資料庫,首先需要匯入 sqflite
和
path
package。
-
sqflite
提供了豐富的類別和方法,以便你能便捷實用 SQLite 資料庫。 -
path
提供了大量方法,以便你能正確的定義資料庫在磁碟上的儲存位置。
執行 flutter pub add
將其新增為依賴:
$ flutter pub add sqflite path
確保你已將 packages 匯入要使用的檔案中。
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
2. 定義狗狗的資料模型
在你準備在新建的表裡儲存狗狗們的訊息的的時候,你需要先定義這些資料。例如,定義一個狗類時,每一條狗狗的資料將包含三個欄位:一個唯一的 id
;名字 name
;年齡 age
。
class Dog {
final int id;
final String name;
final int age;
const Dog({
required this.id,
required this.name,
required this.age,
});
}
3. 開啟資料庫
在你準備讀寫資料庫的資料之前,你要先開啟這個資料庫。開啟一個資料庫有以下兩個步驟:
-
使用
sqflite
package 裡的getDatabasesPath
方法並配合path
package裡的join
方法定義資料庫的路徑。 -
Open the database with the
openDatabase()
function fromsqflite
.
sqflite
package 裡的 openDatabase
方法開啟資料庫。
// 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'),
);
dogs
表
4. 建立 接下來,你需要建立一個表用以儲存各種狗狗的訊息。在這個範例中,建立一個名為 dogs
資料庫表,它定義了可以被儲存的資料。這樣,每條 Dog
資料就包含了一個 id
, name
和 age
。因此,在 dogs
資料庫表中將有三列,分別是 id
, name
和 age
。
-
id
是 Dart 的int
型別,在資料表中是 SQLite 的INTEGER
資料型別。最佳實踐是將id
作為資料庫表的主鍵,用以改善查詢和修改的時間。 -
name
是Dart的String
型別,在資料表中是SQLite的TEXT
資料型別。 -
age
也是Dart的int
型別,在資料表中是SQLite的INTEGER
資料型別。
關於 SQLite 資料庫能夠儲存的更多的資料型別訊息請查閱官方的 SQLite Datatypes 文件。
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. 插入一條狗狗的資料
現在你已經準備好了一個資料庫用於儲存各種狗狗的訊息資料,現在開始讀寫資料咯。
首先,在 dogs
資料表中插入一條 Dog
資料。分以下兩步:
-
把
Dog
轉換成一個Map
資料型別; -
使用
insert()
方法把Map
儲存到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}';
}
}
// 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,
);
}
// Create a Dog and add it to the dogs table
var fido = Dog(
id: 0,
name: 'Fido',
age: 35,
);
await insertDog(fido);
6. 查詢狗狗清單
現在已經有了一條 Dog
儲存在資料庫裡。你可以透過查詢資料庫,檢索到一隻狗狗的資料或者所有狗狗的資料。分為以下兩步:
-
呼叫
dogs
表物件的query
方法。這將回傳一個List <Map>
。 -
將
List<Map>
轉換成List<Dog>
資料型別。
// 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),
];
}
// Now, use the method above to retrieve all the dogs.
print(await dogs()); // Prints a list that include Fido.
Dog
資料
7. 修改一條 使用 sqflite
package 中的 update()
方法,可以對已經插入到資料庫中的資料進行修改(更新)。
修改資料操作包含以下兩步:
-
將一條狗狗的資料轉換成
Map
資料型別; -
使用
where
語句定位到具體將要被修改的資料。
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],
);
}
// 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.
Dog
的資料
8. 刪除一條 除了插入和修改狗狗們的資料,你還可以從資料庫中刪除狗狗的資料。刪除資料用到了 sqflite
package 中的 delete()
方法。
在這一小節,新建一個方法用來接收一個 id 並且刪除資料庫中與這個 id 對應的那一條資料。為了達到這個目的,你必須使用 where
語句限定哪一條才是被刪除的資料。
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],
);
}
範例
執行範例需要以下幾步:
-
建立一個新的 Flutter 工程;
-
將
sqflite
和path
套件新增到pubspec.yaml
檔案裡; -
將以下程式碼貼上在
lib/db_test.dart
檔案裡(若無則新建,若有則覆蓋); -
執行
flutter run lib/db_test.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}';
}
}