为您的Flutter应用程序选择正确的数据库

887 阅读7分钟

我们都会同意,在我们的移动应用开发生命周期的某些阶段,我们会考虑存储和利用数据。这就是数据库派上用场的地方。

什么是数据库?

数据库是一个以结构化方式存储和使用电子信息(数据)的软件,或称数据持久性。数据被可靠地存储(持久化),并可用于工作,除非它被故意删除,这与缓存相反。

数据库允许开发人员使用编程语言或API来保存、读取、修改和删除数据库中的数据。这些活动是在应用程序的后台进行的,远离最终用户的视线。

CRUD是最常见的数据库交互的同义词,它代表了创建、读取、更新和删除。

数据库的类型

在本主题的范围内,我们将主要关注可用于移动技术的两种类型的数据库。数据库可以根据一些因素进行分类,这些因素包括它们支持的数据类型、它们如何扩展、如何定义以及它们的存储位置。

有很多数据库,但我们将坚持使用这两种数据库。

  • 非关系型数据库(NoSQL)
  • 关系型数据库(SQL)

在本指南中,我们将探讨Flutter中的数据库类型,并介绍如何在不长的时间内完成每个数据库的设置。

SQL/关系型数据库

关系型数据库是具有关系和价值的数据集,将它们彼此联系在一起。它们通常以一组数据库表的形式出现,由行和列组成。它们持有关于一个对象的信息,每张表都作为代表对象的蓝图。数据库中的每一列都持有与特定类型有关的数据,而字段则容纳属性的精确值。

表的行表示一个单一数据项的一组相互关联的值。主键是分配给表中每一行的独特标识符,而外键则用于连接我们数据库中其他表格的行。

在不改变数据库表的情况下,这些数据可以以各种方式被访问。

sqflite

SQL,又称关系型数据库,它是任何技术栈中最公认的数据库类型之一。让我们看看Flutter如何做SQL。

sqflite基本上是SQLite的一个实现。它为我们提供了很多功能,使我们能够完全控制我们的数据库,并帮助我们编写查询、关系和我们的应用程序需要的每一个其他数据库功能。

让我们根据Flutter的官方插件页面,看看如何在我们的应用程序中设置这个功能。

为了使用这个包,我们需要在我们的pubspec 文件中添加这个依赖项,如安装页面中所示。

// Get a location using getDatabasesPath
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'demo.db');
// Delete the database
await deleteDatabase(path);
// open the database
Database database = await openDatabase(path, version: 1,
    onCreate: (Database db, int version) async {
  // When creating the db, create the table
  await db.execute(
      'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');
});
// Insert some records in a transaction
await database.transaction((txn) async {
  int id1 = await txn.rawInsert(
      'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
  print('inserted1: $id1');
  int id2 = await txn.rawInsert(
      'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)',
      ['another name', 12345678, 3.1416]);
  print('inserted2: $id2');
});
// Update some record
int count = await database.rawUpdate(
    'UPDATE Test SET name = ?, value = ? WHERE name = ?',
    ['updated name', '9876', 'some name']);
print('updated: $count');
// Get the records
List<Map> list = await database.rawQuery('SELECT * FROM Test');
List<Map> expectedList = [
  {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789},
  {'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416}
];
print(list);
print(expectedList);
assert(const DeepCollectionEquality().equals(list, expectedList));
// Count the records
count = Sqflite
    .firstIntValue(await database.rawQuery('SELECT COUNT(*) FROM Test'));
assert(count == 2);
// Delete a record
count = await database
    .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']);
assert(count == 1);
// Close the database
await database.close();

我们刚才在上面所做的是为我们的数据库创建一个定义的路径和存储名称,这将作为我们设备上的数据库位置。之后,我们利用Database 类实例来打开我们的数据库,这为我们提供了一些功能,包括以下内容。

onCreate :这里我们要定义我们的数据库在创建时的逻辑
OnOpen :数据库打开时运行的代码

我们还向我们的数据库插入了数据,并在控制台中打印出了结果。
随后,我们还可以看到如何更新、删除和查询数据,最后关闭我们的数据库。

漂流

Drift的前身是Moor,是Flutter和Dart的一个反应式持久化库,建立在SQLite之上。

它更像是SQlite包的一个包装,为我们提供了编写结构化关系数据库查询所需的相同功能和工具,它还花时间减少了传统SQLite场景中遇到的模板。

Moor数据库的主要优势在于它可以和build_runner一起使用。你可以在这里找到更多关于build_runner的信息。有了build_runner和Moor,你不需要手动输入所有的查询。你只需创建一个类,指定你想要的行和列作为类中的字段,让使用build_runner的代码生成器生成所需的数据库初始化代码。

为了使用Drift,你必须把它添加到你的pubspec 文件中,并运行flutter pub get 命令来获取你的依赖性,正如这里的文档中所写的。

/////
//// For more information on using drift, please see https://drift.simonbinder.eu/docs/getting-started/
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
part 'main.g.dart';
class TodoItems extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text()();
  TextColumn get content => text().nullable()();
}
@DriftDatabase(tables: [TodoItems])
class Database extends _$Database {
  Database(QueryExecutor e) : super(e);
  @override
  int get schemaVersion => 1;
  @override
  MigrationStrategy get migration {
    return MigrationStrategy(
      onCreate: (m) async {
        await m.createAll();
        // Add a bunch of default items in a batch
        await batch((b) {
          b.insertAll(todoItems, [
            TodoItemsCompanion.insert(title: 'A first entry'),
            TodoItemsCompanion.insert(
              title: 'Todo: Checkout drift',
              content: const Value('Drift is a persistence library for Dart '
                  'and Flutter applications.'),
            ),
          ]);
        });
      },
    );
  }
  // The TodoItem class has been generated by drift, based on the TodoItems
  // table description.
  //
  // In drift, queries can be watched by using .watch() in the end.
  // For more information on queries, see https://drift.simonbinder.eu/docs/getting-started/writing_queries/
  Stream<List<TodoItem>> get allItems => select(todoItems).watch();
}
Future<void> main() async {
  // Create an in-memory instance of the database with todo items.
  final db = Database(NativeDatabase.memory());
  db.allItems.listen((event) {
    print('Todo-item in database: $event');
  });
  // Add another entry
  await db
      .into(db.todoItems)
      .insert(TodoItemsCompanion.insert(title: 'Another entry added later'));
  // Delete all todo items
  await db.delete(db.todoItems).go();
/////

下面是使用Moor(Drift)的几个关键。

它能产生强类型的结果,从而减少运行时出错的机会。它还整合了代码生成,以处理编写我们的查询所涉及的大部分繁重工作。另外,它的功能很丰富,在安卓、iOS、MacOS、网络、桌面和Linux上都支持。

要广泛阅读关于Drift的信息,你可以在这里浏览他们的官方文档网站

地板

受Room持久化包的启发,Floor为你的Flutter应用提供了一个不错的SQLite抽象。它提供了内存对象和数据库行之间的自动映射,以及通过SQL对数据库的完全控制。因此,要完全使用Floor的功能,需要对SQL和SQLite有充分的掌握。

为了使用Floor,基本上有六个步骤,你需要采取。

  1. 添加所需的依赖性:///
    dependencies:
    flutter:
    sdk: flutter
    floor:^1.2.0
    devdependencies:
    floorgenerator
    :^1.2.0
    build_runner:^2.1.2
    ////
  2. 创建一个实体
    我们的实体只是一个标有@entity 注解的类,它是我们希望我们的数据库表的代表或蓝图:// entity/person.dart
    import 'package:floor/floor.dart';
    @entity
    class Person {
    @primaryKey
    final int id;
    final String name;
    Person(this.id, this.name);
    }
    ///
  3. 创建一个DAO(数据访问对象)。

数据访问对象只是让我们访问底层的SQLite数据库。它有一个抽象类,定义了我们需要使用的方法签名,它们都返回一个FutureStream

// dao/person_dao.dart
import 'package:floor/floor.dart';
@dao
abstract class PersonDao {
  @Query('SELECT * FROM Person')
  Future<List<Person>> findAllPersons();
  @Query('SELECT * FROM Person WHERE id = :id')
  Stream<Person?> findPersonById(int id);
  @insert
  Future<void> insertPerson(Person person);
}
///

  1. 创建数据库:///
    //数据库.dart
    //需要的包导入
    import 'dart:async';
    import 'package:floor/floor.dart';
    import 'package:sqflite/sqflite.dart' as sqflite;
    import 'dao/person_dao.dart';
    import 'entity/person.dart';
    part 'database.g.dart'; // generated code will be there
    @Database(version: 1, entities: [Person])
    abstract class AppDatabase extends FloorDatabase {
    PersonDao get personDao;
    }
    ///
  2. 运行代码生成器
    Flutter packages pub run build_runner build 运行生成器。要想自动运行它,只要文件有变化,就使用flutter packages pub run build_runner watch
  3. 使用生成的代码
    任何时候你需要访问你的数据库的一个实例,使用生成的$FloorAppDatabase 类,它为我们提供了对数据库的访问。这个名字是由$Floor 和数据库类的名字组成的。传递给databaseBuilder() 的字符串将是数据库文件名。为了初始化数据库,调用build() ,并确保等待结果。

为了检索PersonDao 实例,调用数据库实例上的personDao getter就可以了。它的函数可以如下所示。

final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();
final personDao = database.personDao;
final person = Person(1, 'Frank');
await personDao.insertPerson(person);
final result = await personDao.findPersonById(1);
///

要获得更多关于Floor的信息,你可以在GitHub上查看官方的例子库,这里

NoSQL/非关系型数据库

这与关系型数据库略有不同,在非关系型数据库中,数据是以非表格形式存储的。存储是基于类似于文档的结构化格式,可以处理详细的信息,同时存储各种各样的数据格式。

说到Flutter中的NoSQL数据库,有几个非常有前途的选择可以考虑,其中最受欢迎的是谷歌Firebase,它是一个在线的利用云存储的数据库,我们还有其他用户定制的选择,如Objectbox、Hive和SharedPreferences。

Firebase

要了解关于Firebase的更多详细信息,同时也要了解他们的代码实验室,你可以到这里的FlutterFire概述页面

我个人喜欢的使用Firebase的主要好处之一是,存储位置是基于云的,这意味着我们可以在多个设备上同步数据,而不是将它们保存在用户的特定设备上。

Firebase提供了不止一个存储数据的选项:我们有Firebase Storage、Firebase Firestore和Realtime Database。每一个都可以根据你的使用情况和要存储的数据类型来选择。对于简单的文件存储,Firebase Firestore的效果非常好。

Firebase也有一个免费计划,其他大多数高级功能都需要付费,但总的来说,Firebase非常好,而且相当容易集成。

Hive

Hive是一个用纯Dart编写的轻量级和极快的键值数据库。

Hive是pub.dev网站上拥有最多人喜欢的存储插件之一,很多人喜欢它的原因是它很容易使用。

这里有一个例子,说明如何设置它并立即开始在你的项目中使用它。

var box = Hive.box('myBox');
box.put('name', 'David');
var name = box.get('name');
print('Name: $name');

看起来太容易了,对吗?嗯,这就是为什么它是Flutter社区中使用最广泛的一个。
除了存储键值对之外,Hive还可以用来存储对象。

@HiveType(typeId: 0)
class Person extends HiveObject {
  @HiveField(0)
  String name;
  @HiveField(1)
  int age;
}
var box = await Hive.openBox('myBox');
var person = Person()
  ..name = 'Dave'
  ..age = 22;
box.add(person);
print(box.getAt(0)); // Dave - 22
person.age = 30;
person.save();

要获得更多信息,请查看pub.dev包页面Hive文档,如果你遇到任何问题,一定要在版本库问题页面上打开一个问题,或者看看现在是否有人出现过这样的问题。

ObjectBox

ObjectBox是一个超级快速的数据库,用于在Flutter本地存储对象。

它有一些很好的功能,就像绝大多数其他的功能一样,其中一些功能包括,可扩展性,静态类型,多平台(这意味着它可以在Android、iOS、Web和桌面上运行),以及对内存的良好表现。

要获得更多关于如何在你的应用程序中使用和实现ObjectBox的本地存储的信息,请查看这里的官方教程

shared_preferences

shared_preferences是移动开发者在其应用程序上本地存储键值对的最常见方式之一,这也是一个相对更容易和更快的选择。

使用shared_preferences的唯一缺点是,它不建议用于存储大块数据,以及列表。

为了在Flutter中使用shared_preferences,你只需添加依赖项并运行Flutter pub get 命令。之后,你创建一个SharedPreferences 的实例并等待它,因为它返回一个Future.x

之后,你使用该实例提供的变量类型回调来保存它,并在需要时使用类似的回调来检索你的数据。这看起来会是这样的。

Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
_prefs.setInt("counter", 1);
var result = _prefs.getInt("counter");

更详细的信息,请查看这里pub.dev页面

最后的思考

Flutter越来越受欢迎,但没有那么多存储数据的选择。然而,尽管如此,现有的软件包可以满足你的需要。上面的指南向我们展示了一些选择和考虑的关键点。

例如,如果你需要存储数据并提供不同设备间的同步,你就必须选择Firebase,如果你不打算连续存储数据,Hive或shared_preferences似乎是个不错的选择。

这一切都归结于你的用例和应用需求。

The post Choosing theright database for your Flutter applicationappeared first onLogRocket Blog.