Flutter常用数据库操作库
Flutter是一种跨平台的移动应用程序开发框架,支持使用多种类型的数据库进行数据存储和管理。Flutter中使用数据库通常需要依赖第三方库来实现,以下是一些常用的Flutter数据库库:
- sqflite:是一个SQLite数据库的Flutter插件,提供了类似于Android中SQLite的API接口,支持基本的CRUD操作。
- firebase_database:是谷歌提供的一种实时的NoSQL数据库,可用于Flutter应用程序的数据存储和同步。
- hive:是一种快速、轻量级的键值对数据库,具有高性能和低延迟的特点,适用于Flutter应用程序中的本地数据存储。
最常用的sqflite介绍
简介
sqflite是一个SQLite数据库的Flutter插件,提供了类似于Android中SQLite的API接口,支持基本的CRUD操作。它是Flutter中使用最广泛的本地数据库库之一,可用于存储应用程序的本地数据。sqflite具有轻量级、高性能和易于使用的特点,适合于小型应用程序或需要简单本地数据存储的应用程序。
举例
下面是一个使用sqflite库的示例代码,演示如何在Flutter应用程序中创建和使用SQLite数据库:
- 添加sqflite库的依赖:
在Flutter项目的pubspec.yaml文件中添加sqflite库的依赖:
dependencies:
sqflite: ^2.2.8+4
- 创建数据库:
在Flutter应用程序中创建一个数据库示例,可以通过执行以下代码来创建一个名为my_database.db
的SQLite数据库:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class MyDatabase {
static final MyDatabase _instance = MyDatabase._internal();
factory MyDatabase() => _instance;
static Database _database;
MyDatabase._internal();
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}
Future<Database> _initDatabase() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'my_database.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute(
'CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)',
);
},
);
}
}
在上述代码中,我们使用了sqflite
库中的openDatabase
方法来创建数据库并指定数据库文件路径。我们还提供了一个onCreate
回调函数,在创建数据库时执行一些初始化操作,例如创建表等。
- 插入数据:
下面是一个示例代码,演示如何向数据库中插入一条数据:
final db = await MyDatabase().database;
await db.insert(
'users',
{'name': 'John'},
conflictAlgorithm: ConflictAlgorithm.replace,
);
在上述代码中,我们首先获取了一个数据库实例,然后使用insert
方法向名为users
的表中插入一条数据。我们将数据存储为一个Map对象,其中键为列名,值为要插入的值。如果表中已经有相同主键的记录,我们使用conflictAlgorithm
参数指定了冲突处理策略,这里使用了ConflictAlgorithm.replace
,表示用新数据替换旧数据。
- 查询数据:
下面是一个示例代码,演示如何从数据库中查询数据:
final db = await MyDatabase().database;
final List<Map<String, dynamic>> users = await db.query('users');
在上述代码中,我们首先获取了一个数据库实例,然后使用query
方法查询名为users
的表中的所有数据。查询结果将返回一个包含多个Map对象的列表,其中每个Map对象表示一条记录,键为列名,值为对应的值。
- 更新数据:
下面是一个示例代码,演示如何更新数据库中的数据:
final db = await MyDatabase().database;
await db.update(
'users',
{'name': 'Mary'},
where: 'id = ?',
whereArgs: [1],
);
在上述代码中,我们首先获取了一个数据库实例,然后使用update
方法更新名为users
的表中的一条数据。我们使用where
参数指定了要更新的记录的条件,这里表示id
为1,使用whereArgs
参数指定了条件中的参数值。我们将要更新的数据存储为一个Map对象,其中键为列名,值为要更新的值。
- 删除数据:
下面是一个示例代码,演示如何从数据库中删除数据:
final db = await MyDatabase().database;
await db.delete(
'users',
where: 'id = ?',
whereArgs: [1],
);
在上述代码中,我们首先获取了一个数据库实例,然后使用delete
方法删除名为users
的表中的一条数据。我们使用where
参数指定了要删除的记录的条件,这里表示id
为1,使用whereArgs
参数指定了条件中的参数值。
依赖sqflite,单例模式封装一个sqlite操作类
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:learning/model/task_model.dart';
class DatabaseHelper {
DatabaseHelper._(); //单例模式
static final DatabaseHelper db = DatabaseHelper._();
static Database? _database;
Future<Database?> get database async {
_database ??= await initDb();
return _database;
}
//init database and open it
Future<Database> initDb() async {
print("initDb");
Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, 'yytimer.db');
print("initDb ${path}");
Database db = await openDatabase(path, version: 1, onOpen: (db) async {
// 等待表创建完成
await db.execute(
'CREATE TABLE IF NOT EXISTS timerdata (id INTEGER PRIMARY KEY, title TEXT, sptime INTEGER, retime INTEGER, cycle INTEGER, setsgap INTEGER, groups INTEGER)');
});
return db;
}
//insert database
Future<int?> insert(Task task) async {
print('insert data Saving Task...');
final db = await database; // 确保database在上下文中定义
try {
var result = await db?.rawInsert(
'INSERT OR REPLACE INTO timerdata (id,title,sptime,retime, cycle,setsgap,groups) VALUES (?,?,?,?,?,?,?)',
[
task.id,
task.title,
task.sptime,
task.retime,
task.cycle,
task.setsgap,
task.groups
]);
print('insert data id timerdata saved! ${task.id}, ${task.title}, ${task.sptime}, ${task.retime},${task.cycle}, ${task.setsgap}, ${task.groups}');
return result;
} on DatabaseException catch (e) {
print(e);
return -1;
}
}
//query database
Future<List<Task>> getAll() async {
print('getAll database...');
var db = await database;
var query = await db?.query('timerdata', orderBy: 'id DESC');
List<Task> tasks =
query!.isNotEmpty ? query.map((t) => Task.fromMap(t)).toList() : [];
print('getAll in database: ${tasks.length} , ${tasks[0].title},${tasks[1].title}');
return tasks;
}
//delete sql by id
Future<void> delete(int id) async {
var db = await database;
await db?.rawDelete('DELETE FROM timerdata WHERE id = ?', [id]);
}
//delete sql by title
Future<void> deleteByTitle(String title) async {
var db = await database;
await db?.rawDelete('DELETE FROM timerdata WHERE title = ?', [title]);
}
//update database by id
Future<void> updateDatabase(Task task) async {
final db = await database;
await db?.update(
'timerdata',
task.toMap(),
where: "id = ?",
whereArgs: [task.id],
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
//update database by title
Future<void> updateDatabaseByTitle(Task task) async {
final db = await database;
await db?.update(
'timerdata',
task.toMap(),
where: "title = ?",
whereArgs: [task.title],
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
在initDb()中,可以使用await db.execute()来等待数据库表的创建完成,避免在创建表之前使用数据库出现问题。
在insert()方法中,可以使用INSERT OR REPLACE语句来实现插入或更新操作,以避免插入重复数据。
在getAll()方法中,可以使用db!.query(‘timerdata’, orderBy: ‘id DESC’)来按照id倒序获取所有数据,这样可以避免获取数据后再排序的操作。
在updateDatabase()和updateDatabaseByTitle()方法中,可以使用update()方法的conflictAlgorithm参数来实现插入或更新操作,以避免更新重复数据的问题。
说明
initDb说明
onCreate是在第一次打开数据库时调用的回调函数,用于创建数据库表和初始化数据。在Dart的sqflite库中,onCreate回调函数的签名为(Database db, int version),其中db参数是打开的数据库对象,version参数是数据库的版本号。
在onCreate回调函数中,我们可以使用db.execute()方法来执行SQL语句,以创建表和初始化数据。例如,在以下代码中:
Database db = await openDatabase(path, version: 1, onCreate: (Database db, int version) async {
await db.execute('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
await db.execute('INSERT INTO users (name) VALUES ("Alice")');
});
我们创建了一个名为users的表,并插入了一条记录。具体来说,我们使用了CREATE TABLE IF NOT EXISTS语句来创建一个users表,该表有两个字段:id和name,其中id是主键,name是文本类型。然后,我们使用INSERT INTO语句向users表中插入一条记录,该记录的name字段为"Alice"``onCreate是在第一次打开数据库时调用的回调函数,用于创建数据库表和初始化数据。在Dart的sqflite库中,onCreate回调函数的签名为(Database db, int version),其中db参数是打开的数据库对象,version参数是数据库的版本号。
conflictAlgorithm说明
conflictAlgorithm
是在数据插入或更新时发生冲突(例如违反唯一性约束)时的解决策略。在Dart的sqflite
库中,有以下四种冲突解决策略:
ConflictAlgorithm.rollback
:回滚事务,放弃所有更改。ConflictAlgorithm.abort
:放弃当前操作,但不回滚事务。ConflictAlgorithm.fail
:放弃当前操作,但是不回滚事务,并抛出异常。ConflictAlgorithm.replace
:替换旧数据,或者插入新数据,以解决冲突。
在代码中,ConflictAlgorithm.replace
的作用是在插入或更新数据时,如果发生冲突,则用新数据替换旧数据。这种策略可以保证数据的唯一性,并且不会抛出异常。如果数据不存在,则直接插入新数据,如果数据已经存在,则用新数据替换旧数据。
例如,在上面的代码中,updateDatabase()
和updateDatabaseByTitle()
方法中使用了conflictAlgorithm: ConflictAlgorithm.replace
参数,以避免更新或插入重复数据。如果表中已经存在具有相同id或title的数据,则会用新数据替换旧数据,否则直接插入新数据。
之前写的界面,数据用数据库保存后的效果如下图: