演示Dart包,dbutils。
文章介绍了Dart包dbutils。这是SQLite的插件sqflite由[Alex Tekartik编写。该插件知道如何与SQLite数据库“交谈”,而Dart包知道如何与插件“对话”。最终结果使您可以更轻松地操作SQLite数据库。在继续之前,我建议现在安装Dart包,因为它包含了本文中演示的相同的示例应用程序。按照以下三个步骤,您就可以在Flutter应用程序中轻松使用SQLite数据库了。
pub.dev上的dbutils
与往常一样,我更喜欢使用屏幕截图来显示我的文章中的代码。我觉得它们更容易使用,更容易阅读。但是,您可以单击/点击它们以将代码视为要点或在Github中查看。具有讽刺意味的是,最好在计算机上而不是手机上阅读有关移动开发的文章。因为我们主要在我们的计算机上编程; 不在我们的手机上。
让我们开始。
在示例应用程序中,我们有一个类Employee,它扩展了名为DBInterface的类库。它本身位于libary文件Employee.dart中,并实现了三个必需的属性:两个名为name和version的getter 以及一个名为onCreate()的函数。name是包含您将在函数onCreate()中定义的所有数据库表的名称。version当然是数据库的版本号。到目前为止非常简单。
Employee.dart
因此,在上面的屏幕截图中,您可以看到构成Dart文件Employee.dart的内容。查看onCreate()函数,您将意识到它需要您熟悉SQL因为它用于创建和操作数据表。在Employee类的末尾列出的功能是用于保存对Employee记录的任何更改,包括编辑现有记录或创建全新记录。有删除Employee记录的功能,以及从SQLite数据库中检索Employee记录的功能。列出的最后一个函数提供了一个“空”员工记录,通常在创建全新的员工记录时使用。
注意,我已选择为此Employee类使用工厂构造函数。这样做会强制执行Joshua Bloch现在著名的2001书籍Effective Java的第1项中描述的单例模式。因此,对于此类的每个后续实例化,仅使用Employee的“一个实例”。我们不希望在此应用中运行“Employee”表的多个实例。
在下面的屏幕截图中,您可以看到关键字factory,允许在构造函数中使用return语句。在这种情况下,它返回静态属性_this,它将包含此类的唯一实例。
Employee class
例如,转到查看员工信息的“员工详细信息”屏幕,您可以选择删除该记录。因为它不是一个常见的操作,所以不需要定义一个内存变量来获取Employee类的实例化引用,只需调用构造函数。我们知道,到目前为止,我们访问此屏幕时,Employee类已经被实例化,并将返回该实例。
MyEmployeeState class
此外,每个相同的类都有函数_submit,调用它来保存对员工信息所做的任何更改。顺便说一下,它还用于保存全新的员工记录。注意,它也调用构造函数Employee()来获取该实例来保存该员工记录。
MyEmployeeState class
查看示例应用程序时,您将看到在State对象的initState()函数中首先实例化Employee表。在大多数情况下,这是适当的地方。实际上,对于大多数Dart包和类库,通常的做法是分别在State对象的initState()和dispose()函数中初始化和销毁它们。因此,在大多数情况下,dbutilsDart包的有一个init打开的数据库函数,以及disposed关闭数据库函数。但请注意,在下面的屏幕截图中,init()*函数调用已被注释掉。
EmployeeListPageState类
它被注释掉,以证明只要有要执行的查询,库类就能够打开数据库。上面突出显示的getEmployees()函数就是这种情况。因此,在下面的屏幕截图中,在类库的深处,最终调用的rawQuery()函数将打开数据库(如果尚未打开)。
_DBInterface class
您可以在getEmployees()函数中看到执行SQL语句以检索可在表Employee中找到的所有记录。它返回一个Map对象列表 - 在Employee表中找到的任何和所有记录。当然,每个Map对象的关键字是字段名称。注意,该类有一个Map对象,值,它接收表中的最后一条记录,如果表也是空的,则为空记录。
Employee class *
当然,我们也可以删除该行的注释,并在initState()函数中调用init()。实际上,这样做会更好,因为它会为代码增加一点点一致性。您可以在下面的库的屏幕截图中看到,数据库在init()函数中打开。然后,在State对象的dispose()函数中调用dispos()或close()函数较为合适,以确保在终止应用程序时正确关闭数据库。
Flutter的SQFlite插件sqflite处理Map对象。此类库继续该方法,并允许您使用Map对象为表分配值。以下是“Employee”屏幕的屏幕截图,其中显示了单个员工的详细信息。注意,Map对象employee用于将员工的信息传递给显示的TextFormField Widgets,并接受用户输入的任何新值。您可以看到Map对象用于删除所需的Employee记录的位置。而在另一端,Map对象进一步用于将任何新信息保存到数据表。
image.png
MyEmployee class *
在示例应用程序中,单击标记为“ 保存”的按钮以保存员工信息。查看下面的代码,您会看到调用Employee类中定义的save()函数来执行操作。正如您现在可能已经猜到的那样,涉及数据库的大多数操作都是异步的,因此我们正在使用Future对象。在这种情况下,save()函数返回一个类型为Boolean,Future <bool>的Future对象,然后我们使用回调函数then(),然后通知用户保存是否成功以及返回上一个屏幕。
MyEmployee class *
在Employee类中,save()函数又调用saveRec()函数。它传递包含员工信息的Map对象以及要更新的数据表的名称。
image.png
Employee class
DBInterface class *
您可以看到函数updateRec()依次被调用以将数据深入到Dart包的类库中。我们现在正在使用Future对象。你可以在下面看到,我们越来越接近插件本身调用一个带有自己的updateRec()函数的内部'helper'类。如您所见,它包含在try..catch语句中。如果出现错误,另一个名为_dbError的“helper”类将记录错误并且不会使整个应用程序崩溃。
image.png
DBInterface class *
最后,内部类_dbInt使其updateRec()函数执行实际的数据库操作。请注意,如果键字段值为null,则表示这是一个新的Employee记录,因此插件db将调用其insert()函数将此新记录添加到数据库内的数据表中。否则,它是现有记录,并使用键字段值更新数据表记录。
_DBInterface class *
当这个示例应用程序启动时,它将生成到目前为止输入的员工列表。下面是负责显示该列表的代码。使用FutureBuilder小部件执行“Employee”数据表的查询,当有数据时,列出每个雇员的名字和姓氏。
MyEmployeeList class *
请注意上面屏幕截图中的最后一个小红色箭头。它揭示了如何生成“空”Employee Map对象并将其传递到“员工”屏幕,以便用户可以输入全新的员工。
作为一个计数器,让我们来看看删除Employee记录时涉及的过程。让我们回到显示单个员工的“员工”屏幕,并注意AppBar上有“垃圾”图标。你按下那个图标; 你将调用Employee类的deleteRec()函数。注意,它传递包含员工信息的Map对象。特别是,该Employee记录的关键字段ID。
[MyEmployee class *
在deleteRec()函数中,调用delete()函数传递数据表的名称以及字段名称id指定的键字段值。注意,如果没有Map对象传递给函数,则该类转向其内部Map对象的值,希望提供id值。
Employee class *
此外,在dbutils类库中,try..catch语句再次阻止应用程序在出现异常时全部崩溃。同样,内部帮助器类可以调用自己的delete()函数来执行实际删除。
DBInterface class *
最终函数在成功操作时返回int类型的Future对象(已删除的记录数)。它也需要键字段值来查找适当的Employee记录。请注意,如果数据库尚未打开,则会在执行删除之前打开它。
image.png
_DBInterface class *
让我们使用本文的其余部分从上到下遍历此类库DBInterface.dart。它将向您展示Alex Tekartik的插件在何处以及如何与SQLite数据库一起使用以及如何在您自己的Flutter应用程序中利用这一事实。
在使用此类库时,您最多可以覆盖五个函数。每个处理五个不同的事件:onCreate,onConfigure,onOpen,onUpgrade和onDowngrade。
函数onCreate()是一个抽象函数,必须实现。其他只需要在需要时才能被覆盖。下面,在代码中,您可以在注释中阅读实现它们的时间和原因。
DBInterface class
库的构造函数的屏幕截图显示它有一个“初始化列表”,其中最后的“library-private”变量被赋予了处理可能发生的任何错误的类。它是helper类,_ DBError,也在此库文件中定义。在构造函数本身内部,我们看到另一个库私有变量被赋予另一个名为_dbInt的 helper类。正是这个类接受了上面提到的这五个例程,并且这个类在这里完成了最重要的工作,直接在其指定的操作中使用SQFlite插件。
DBInterface class
在五个例程之后,我们将看到接下来打开和关闭数据库所涉及的功能。同样,我们看到init()函数和dispos()函数常用于与State对象“接口”。您还会看到'helper'类_dbInt实际上尝试打开数据库。如果它无法打开数据库,则另一个“帮助程序”类将用于记录生成的异常。
DBInterface class
接下来,是类库提供的所有getter。大多数人都关注确定处理数据库时可能发生的常见错误的类型。但是,第一个getter允许您访问数据库中找到的每个表的字段名称。接下来是返回每个表的关键字段名称的函数。更新像SQLite这样的关系数据库中的记录时的一个基本功能。
接下来是在需要时提供“空”记录的getter。之后,您会看到您可以以数据库引用db的形式访问插件本身。最后,确定所有这些getter提供的数据库可能出现的许多错误所需的任何和所有信息。
类库中的下一部分代码负责将数据保存到数据库。你以前见过的第一个功能。这是saveRec()函数。反过来,它再次调用函数updateRec(),该函数又调用辅助类_dbInt中的updateRec()函数。
之后是函数saveMap()。它旨在从多个表中获取Map对象记录,但仅保存指定表的那些记录。专业功能。但是,这对于像这样的类库是可以预期的。在使用这个SQLite插件时,这会让生活变得更轻松。
接下来是runTxn()函数。不可否认,它尚未经过全面测试,但我编写它来管理插件的跨国功能。试一试!让我知道它对你有用。
请注意,如果发生错误,下一个函数updateRec()将返回一个“空”Map对象。出现错误时,异常将记录在另一个'helper'类_dbError中。如果更新成功,则之前可能已记录的任何先前异常将被清除。
DBInterface class
接下来,是用于检索记录或删除记录的代码部分。第一个函数getRecord()使用在整数变量id中找到的主键字段值返回一条记录(如果有)。您可以看到它只调用第二个函数getRow(),但是从_dbInt中引用的'helper'类提供了Map对象_fields。这个Map对象列出了当前数据库中找到的所有表的所有字段,但是使用其第一个参数table为函数指定了特定的感兴趣的表。如果存在错误,则在List中返回空的Map对象。
我们已经访问过此代码中的第三个函数。它是delete()函数并返回已删除的行数(即记录)。当然,在大多数情况下,您都有一个规范化的数据表,因此在按主键字段搜索时,该数字应始终为1。如果出现问题,则返回一个空的List对象。
当处理这个SQFlite插件时,sqflite在处理Future对象,因此,所有函数也返回Future对象。
image.png
DBInterface class
接下来的两个函数调用插件的查询功能。第一个函数rawQuery()使用'原始'SQL语句,而接下来的两个函数getTable()和query()使用特定的参数列表,这些参数被识别为传统上在SQL Select语句中找到的选项。第二个函数getTable()只需要表的名称来执行查询。字段名称列表由Map对象_dbInt._fields提供。剩余的“命名参数”构成SQL Select选项。你可以看到第二个函数getTable(),只调用第三个函数,query()提供与指定表关联的字段列表。
DBInterface class *
类中的最后两个函数DBInterface用于直接查询SQLite中的“‘system tables”。在这种情况下,一个列出数据库中找到的表名,另一个列出特定数据表的字段名。
DBInterface class *
接下来的两个函数是记录可能发生的任何异常。注意setError()函数如何将它作为异常记录,即使传递的对象不是一个。该getError()返回异常,但不是'清除它的第一次之前。现在就准备接收可能发生的任何未来异常。最后,请注意它们都是静态函数。这意味着记录与数据库有关的任何错误。即使有多个类的实例。
DBInterface class *
“helper”类_DBError在接下来列得Dart文件DBInterface.dart中。它用于记录处理数据库时可能发生的任何和所有错误。函数set()位于类库中可能发生错误的多个位置。错误记录在实例变量中:_message和_e。注意,set()函数返回String,_message,描述记录的错误。功能,clear(),也在整个类库中调用。对于每个成功的数据库操作,这两个实例变量都被“清除” - 因此不要错误地指示上次成功的数据库操作失败。
注意:这意味着,对于每个数据库操作,如果最后执行的数据库操作成功,则可以检查getter,inError或noError。
image.png
DBInterface class *
随后的getter和函数可以帮助您确定最后一次数据库操作是否导致错误,如果是,则确定可能发生的错误类型。
'library-private'类, _DBInterface确实可以实现繁重的实际执行所有数据库操作。您将在构造函数中将参数识别为类库构造函数中的参数。
DBInterface class
前两个功能是打开和关闭数据库。当然,这里涉及Future对象。在第一个函数_open()中,返回一个布尔值以指示数据库是否已打开。名为localpath的getter使用名为
getApplicationDocumentsDirectory()的标准Flutter函数来确定数据库在手机上的实际位置。最后,函数调用_tableFields()用于收集驻留在该数据库中的表名以及它们各自的字段名。全部进入适当的Map对象。
_DBInterface class
接下来是负责“更新”现有记录或创建新记录的代码。如果存在错误,则实例变量rowsUpdated将包含值零。否则,可能是一个值,因为我们要么创建一个新记录,要么使用其主键更新一个记录。因此,根据主键字段是空还是已经分配了整数值,它会在这里进行'插入'或'更新'。
_DBInterface class *
接下来,是用于通过查找其主键从表中检索记录的函数。请注意,如果实例变量db为null,则此函数将打开数据库。如果类库已正确“初始化”,则不可能为null,但如果没有,则此类库将首先尝试打开数据库来尝试获取记录。如果失败,则返回“空”Map的“空”列表。
_DBInterface class *
下一个函数删除记录。它返回已删除的记录数。在大多数情况下,它将是一个整数。如果找不到记录,或者由于某种原因,数据库无法打开,它将返回零值。
_DBInterface class *
接下来的两个函数rawQuery()和query()由类库调用,DBInterface在try..catch语句中调用。然而,在这两种情况下,它的SQFlite插件,DB,即实际执行查询。第二个函数query()将传统上可用于SQL Select语句的许多选项传递给SQFlite插件。
_DBInterface class
接下来的两个函数主要由函数_tableList()使用。正是这两个直接查询数据库中的“system table”,在这种情况下,分别列出数据库中找到的表名和特定数据表的字段名。
_DBInterface class *
最后,在类库的末尾是函数_tableList()。在函数_open()中打开数据库时调用它。正是这个函数用两个Map对象_fields和_keyFields填充数据库中包含的表的所有字段名称以及它们各自的键字段名称。
最后,它填充一个名为_newRec的Map对象的Map对象,其中包含每个表的“空”值。同样,主要用于创建特定表的全新记录时。
_DBInterface class *
本文的目的是强调如何使用Dart包在Flutter应用程序中使用数据库时花费一些精力。这是一种操纵数据表及其记录的一致方法。该软件包将在未来进行扩展,不仅可以与SQLite一起使用,还可以使用任何未来的插件访问许多其他类型的数据库 - 无论是合理的还是其他的。
翻译自:https://medium.com/@
greg.perry/sqlite-database-in-flutter-2ef1ef87e5af