编写一个实时的NativeScript应用程序:SQLite
NativeScript 是一个使用 XML、CSS 和 JavaScript 构建跨平台本机移动应用程序的框架。在本系列中,我们将尝试使用 NativeScript 应用程序可以完成的一些很酷的事情:地理位置和 Google 地图集成、SQLite 数据库、Firebase 集成和推送通知。在此过程中,我们正在构建一个具有实时功能的健身应用程序,该应用程序将使用这些功能。
在本教程中,您将学习如何将 SQLite 数据库集成到应用程序中以在本地存储数据。具体来说,我们将存储在上一教程中收集的步行会话数据。
您将要创建的内容
继续上一教程,您将添加一个选项卡视图来显示应用程序的不同部分。以前我们的应用只有跟踪页面,因此我们不需要选项卡。在这篇文章中,我们将添加步行页面。该页面将显示用户的步行会话。每次用户跟踪其步行过程时,都会在此处添加一个新的数据点。还会有清除数据的功能。
最终输出如下:
设置项目
如果您遵循了上一篇关于地理定位的教程,则可以简单地使用同一项目并构建我们将在本教程中添加的功能。否则,您可以创建一个新项目并将起始文件复制到项目的 app 文件夹中。
tns create fitApp --appid "com.yourname.fitApp"
之后,您还需要安装地理位置和 Google 地图插件:
tns plugin add nativescript-geolocation tns plugin add nativescript-google-maps-sdk
安装后,您需要配置 Google 地图插件。您可以阅读上一教程中的安装 Google 地图插件部分,了解有关如何执行此操作的完整说明。
完成所有这些后,您应该准备好按照本教程进行操作。
运行项目
可以通过执行 tns run android
来运行项目。但由于这个应用程序将构建在地理定位功能的基础上,我建议您使用 GPS 模拟器来快速设置和更改您的位置。您可以在上一教程的运行应用部分了解如何执行此操作。
安装 SQLite 插件
开始使用 SQLite 需要做的第一件事是安装插件:
tns plugin add nativescript-sqlite
这允许您执行诸如连接到数据库并对其执行 CRUD(创建、读取、更新、删除)操作之类的操作。
连接到数据库
打开 main-page.js 文件并导入 SQLite 插件:
var Sqlite = require("nativescript-sqlite");
您现在可以连接到数据库:
var db_name = "walks.db"; new Sqlite(db_name).then(db => { // next: create table for storing walks data }, error => { });
walks.db 文件是使用 touch
命令从终端创建的,因此它只是一个空文件。将其复制到app文件夹中。
如果连接成功,则将执行promise的resolve函数。在其中,我们运行 SQL 语句来创建 walks
表。为了简单起见,我们需要保存的是总距离(以米为单位)和总步数,以及开始和结束时间戳。
db.execSQL("CREATE TABLE IF NOT EXISTS walks (id INTEGER PRIMARY KEY AUTOINCREMENT, total_distance INTEGER, total_steps INTEGER, start_datetime DATETIME, end_datetime DATETIME)").then(id => { page.bindingContext = createViewModel(db); }, error => { console.log("CREATE TABLE ERROR", error); });
查询成功执行后,我们将数据库实例 (db
) 传递到页面上下文中。这将使我们稍后可以从 main-view-model.js 文件中使用它。
获取数据
现在我们已准备好处理数据。但由于我们将处理日期,因此我们首先需要安装一个名为 fecha 的库。这使我们能够轻松解析和格式化日期:
npm install --save fecha
安装完成后,打开 main-view-model.js 文件并包含该库:
var fecha = require('fecha');
接下来是检查地理定位是否启用的代码。首先,创建一个变量(walk_id
)用于存储步行记录的ID。我们需要这个,因为当用户开始位置跟踪时,应用程序会立即将新的步行记录插入到 walks
表中。 walk_id
将存储 SQLite 自动生成的 ID,以便我们能够在用户停止跟踪时更新记录。
var walk_id;
接下来,获取当前月份和年份。我们将使用它来查询表,以便它只返回同一月份和年份的记录。这使我们能够限制用户界面中显示的记录数量。
var month = fecha.format(new Date(), 'MM'); //e.g 07 var year = fecha.format(new Date(), 'YYYY'); //e.g 2017
我们还需要一个变量来存储开始时间戳。稍后我们将使用它来更新 UI。这是因为我们只在加载应用程序时查询表一次,因此我们需要手动更新可用的任何新数据的 UI。由于起始时间戳仅在用户开始跟踪时才具有值,因此我们需要在范围之外对其进行初始化,以便稍后更新或访问其值。
var st_datetime; // start datetime
初始化将在 UI 中显示的步行数据:
var walks = []; viewModel.walks = []; viewModel.has_walks = false;
使用 all()
方法从 walks
表获取数据。在这里,我们提供月份和年份作为查询参数。 strftime()
函数用于提取 start_datetime
的月份和年份部分。
db.all( "SELECT * FROM walks WHERE strftime('%m', start_datetime) == ? AND strftime('%Y', start_datetime) == ? ORDER BY start_datetime DESC", [month, year] ).then((err, rs) => { if(!err){ // next: update the UI with the walks data } });
返回成功响应后,我们将循环访问结果集,以便可以正确格式化数据。请注意,我们访问各个值的索引取决于前面在 main-page.js 文件中描述的表结构。第一列是 ID,第二列是总距离,依此类推。
格式化的数据随后被推送到 walks
数组并用于更新 UI。 has_walks
用作 UI 的切换开关,以便我们可以根据其值显示或隐藏内容。
rs.forEach((w) => { let start_datetime = new Date(w[3]); let end_datetime = new Date(w[4]); walks.push({ start: fecha.format(start_datetime, 'MMM D, h:mm'), // e.g Jun 5, 5:30 end: fecha.format(end_datetime, 'h:mm a'), // e.g 6:30 pm distance: commafy(w[1]) + 'm', // e.g 2,000m steps: commafy(w[2]) // e.g 2,300 }); }); if(walks.length){ viewModel.set('has_walks', true); } viewModel.set('walks', walks);
这将为main-page.xml文件中的 ListView
提供数据:
<ListView items="{{ walks }}"> <ListView.itemTemplate> <GridLayout columns="2*,*,*" rows="auto" class="item item-row"> <Label text="{{ start + ' - ' + end }}" textWrap="true" row="0" col="0"/> <Label text="{{ distance }}" textWrap="true" row="0" col="1" /> <Label text="{{ steps }}" textWrap="true" row="0" col="2" /> </GridLayout> </ListView.itemTemplate> </ListView>
保存数据
用户开始跟踪后,将当前日期时间设置为 start_datetime
并使用 execSQL()
函数将初始值插入表中。就像 all()
函数一样,它期望 SQL 查询作为第一个参数,参数数组作为第二个参数。
如果查询成功,它应该返回插入记录的自动生成的 ID。然后,我们将其指定为 walk_id
的值,以便稍后可以使用它来更新此特定记录。
st_datetime = new Date(); var start_datetime = fecha.format(st_datetime, 'YYYY-MM-DD HH:mm:ss'); db.execSQL( "INSERT INTO walks (total_distance, total_steps, start_datetime) VALUES (?, ?, ?)", [0, 0, start_datetime] ).then((id) => { walk_id = id; }, (e) => { dialogs.alert(e.message); });
一旦用户停止跟踪,我们将再次获取当前时间戳并相应地格式化以进行存储:
var ed_datetime = new Date(); var end_datetime = fecha.format(ed_datetime, 'YYYY-MM-DD HH:mm:ss');
由于我们按照从最近到最近的顺序对结果进行排序,因此我们使用 unshift()
(而不是 push()
)将新项目添加到walks
数组的顶部。
walks.unshift({ start: fecha.format(st_datetime, 'MMM D, h:mm'), end: fecha.format(ed_datetime, 'h:mm a'), distance: commafy(total_distance) + 'm', steps: commafy(total_steps) }); viewModel.set('walks', walks); if(walks.length > 0){ viewModel.set('has_walks', true); }
之后,我们再次使用 execSQL()
函数来更新我们之前插入的记录:
db.execSQL( "UPDATE walks SET end_datetime = ?, total_steps = ?, total_distance = ? WHERE id = ?", [end_datetime, total_steps, total_distance, walk_id] ).then( (err, id) => { if(!err){ // todo: add code for resetting the tracking UI here } } );
请务必将用于重置跟踪 UI 的代码(重置总距离和步数)移至 Promise 的解析函数内,以便您可以轻松测试更新查询是否成功执行。
清除数据
通过点击步行数据列表下方的清除数据按钮即可删除数据:
<ListView items="{{ walks }}"> ... </ListView> <Button text="Clear Data" tap="{{ clearData }}" class="bg-danger" />
在 main-view-model.js 文件中,添加用于删除 walks
表中所有数据的代码。如果您习惯使用 MySQL,您可能想知道为什么我们使用 DELETE
查询而不是 TRUNCATE
来清空表。嗯,那是因为 SQLite 没有 TRUNCATE
函数。这就是为什么我们必须使用 DELETE
查询而不提供条件,以便它将删除表中当前的所有记录。
viewModel.clearData = function() { dialogs.confirm("Are you sure you want to clear your data?").then((agree) => { if(agree){ db.execSQL("DELETE FROM walks", []).then( (err) => { if(!err){ dialogs.alert("Data has been cleared!"); walks = []; viewModel.set('walks', []); viewModel.set('has_walks', false); } } ); } }); }
结论
在本教程中,您学习了如何使用 SQLite 插件在 NativeScript 应用程序中本地存储数据。正如您所看到的,SQLite 允许您重用现有的 SQL 技能来管理本地数据库。需要注意的是,并非您在 MySQL 中使用的所有函数都在 SQLite 中受支持。因此,如果您不确定是否支持某个功能,查阅文档总是明智的。
如果您想了解在 NativeScript 应用程序中存储数据的其他选项,我建议您阅读这篇有关使用 NativeScript 脱机的文章。
在本系列的最后一篇文章中,我们将向我们的应用添加推送通知。
同时,请查看我们关于 NativeScript 和跨平台移动编码的其他一些帖子。
有关 NativeScript 的全面介绍,请尝试我们的视频课程“使用 NativeScript 编写移动应用程序”。在本课程中,Keyvan Kasaei 将逐步向您展示如何构建一个简单的应用程序。在此过程中,您将学习如何使用网络请求、MVVM 架构以及一些最重要的 NativeScript UI 组件来实现简单的应用程序工作流程。最后,您将了解为什么应该在下一个移动应用项目中考虑使用 NativeScript。