【Flutter 实战】大量复杂数据持久化
老孟导读:上一篇文章讲解了 Android 和 iOS 的文件目录系统,此篇文章讲解如何使用 SQLite 保存大量复杂数据数据。欢迎大家投稿:http://laomengit.com/plan/Contribution.html
保存数据到本地是应用程序非常重要的功能之一,比如如下场景:一个新闻类或者博客类的应用程序,打开后进入首页,如果本地没有保存数据,则需要通过网络获取数据,在返回数据之前,用户看到的是空白页面,而如果本地保存了部分新闻,则显示这部分数据,等待最新的数据返回时在刷新即可,对用户体验来说,明显第二种体验更佳。
SQLite 是目前最受欢迎的本地存储框架之一,此篇文章将会介绍如何使用 SQLite 保存、查询、删除数据等。
SQLite pub地址:https://pub.flutter-io.cn/packages/sqflite SQLite Github:https://github.com/tekartik/sqflite SQLite 讲解:https://www.sqlitetutorial.net/
如果您对数据库和 SQL 语句相关知识有一点了解,下面的讲解会比较容易理解,如果对数据库和 SQL 语句相关知识一点不了解,那下面的讲解理解起来可能会有一定的难度。
那是否需要先去学习数据库和 SQL 语句相关知识?我觉得没有必要,应用程序使用的数据库和 SQL 语句相关知识都是非常基础的,比如打开数据库、创建表、SQL 查询语句、更新语句、删除语句等,这些都是固定的格式,固定语句记住即可。
添加依赖
SQLite 并不是 Flutter 系统自带的,而是一个第三方插件,在项目的 pubspec.yaml
文件中添加依赖:
dependencies:
sqflite: ^1.3.1
path_provider: ^1.6.11
执行命令:
flutter pub get
使用 SQLite 创建数据库的时候需要本地路径做为参数,所以添加path_provider
插件获取本地路径。
单例模式创建 SQLite 访问
使用 SQLite 并不是一定要使用单例模式,单例模式是为了保证整个应用程序仅有一个数据库实例和全局访问。
class DBProvider{
static final DBProvider _singleton = DBProvider._internal();
factory DBProvider() {
return _singleton;
}
DBProvider._internal();
}
初始化数据库
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
class DBProvider {
static final DBProvider _singleton = DBProvider._internal();
factory DBProvider() {
return _singleton;
}
DBProvider._internal();
static Database _db;
Future<Database> get db async {
if (_db != null) {
return _db;
}
_db = await _initDB();
return _db;
}
Future<Database> _initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, 'dbName');
return await openDatabase(path,
version: 1, onCreate: _onCreate, onUpgrade: _onUpgrade);
}
///
/// 创建Table
///
Future _onCreate(Database db, int version) async {}
///
/// 更新Table
///
Future _onUpgrade(Database db, int oldVersion, int newVersion) async {}
}
创建 Table,Table 代表一张表,下面创建一张用户表,表列有 id(唯一标识)、name(姓名)、age(年龄)、sex(性别)。
///
/// 创建Table
///
Future _onCreate(Database db, int version) async {
return await db.execute("CREATE TABLE User ("
"id integer primary key AUTOINCREMENT,"
"name TEXT,"
"age TEXT,"
"sex integer"
")");
}
保存数据
先创建一个 User 的 Model 类,用于数据的保存:
class User {
int id;
String name;
int age;
int sex;
User({this.id, this.name, this.age, this.sex});
User.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
age = json['age'];
sex = json['sex'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['age'] = this.age;
data['sex'] = this.sex;
return data;
}
}
保存数据:
Future saveData(User user) async {
var _db = await db;
return await _db.insert('User', user.toJson());
}
案例:输入姓名、年龄、性别,点击保存
class _AddUser extends StatefulWidget {
@override
__AddUserState createState() => __AddUserState();
}
class __AddUserState extends State<_AddUser> {
String _radioGroupValue = '0';
TextEditingController _nameController;
TextEditingController _ageController;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
_ageController = TextEditingController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('保存数据'),
),
body: Column(
children: <Widget>[
Row(
children: <Widget>[
Text('姓名:'),
Flexible(
child: TextField(
controller: _nameController,
),
),
],
),
Row(
children: <Widget>[
Text('年龄:'),
Flexible(
child: TextField(
controller: _ageController,
),
),
],
),
Row(
children: <Widget>[
Text('性别:'),
Flexible(
child: RadioListTile(
title: Text('男'),
value: '0',
groupValue: _radioGroupValue,
onChanged: (value) {
setState(() {
_radioGroupValue = value;
});
},
),
),
Flexible(
child: RadioListTile(
title: Text('女'),
value: '1',
groupValue: _radioGroupValue,
onChanged: (value) {
setState(() {
_radioGroupValue = value;
});
},
),
),
],
),
Builder(
builder: (context) {
return RaisedButton(
child: Text('保存'),
onPressed: () async {
var user = User(
name: '${_nameController.text}',
age: int.parse('${_ageController.text}'),
sex: int.parse('$_radioGroupValue'));
int result = await DBProvider().saveData(user);
if (result > 0) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('保存数据成功,result:$result'),
));
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('保存数据失败,result:$result'),
));
}
},
);
},
)
],
),
);
}
}
使用 SQL 语句保存数据:
Future rawInsert(User user) async {
var _db = await db;
return await _db.rawInsert(
'INSERT Into User (name,age,sex) VALUES (?,?,?)',[user.name,user.age,user.sex]);
}
查询数据
查询全部数据:
Future<List<User>> findAll() async {
var _db = await db;
List<Map<String, dynamic>> result = await _db.query('User');
return result.isNotEmpty ? result.map((e) {
return User.fromJson(e);
}).toList():[];
}
将查询的数据显示在表格上:
class DatabaseDemo extends StatefulWidget {
@override
_DatabaseDemoState createState() => _DatabaseDemoState();
}
class _DatabaseDemoState extends State<DatabaseDemo> {
List<User> _list = [];
@override
void initState() {
super.initState();
_loadData();
}
_loadData() async {
_list = await DBProvider().findAll();
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sqlite Demo'),
),
body: Column(
children: <Widget>[
Row(
children: <Widget>[
RaisedButton(
child: Text('查询数据'),
onPressed: (){
_loadData();
},
),
RaisedButton(
child: Text('添加数据'),
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return _AddUser();
}));
},
),
],
),
Table(
children: [
TableRow(children: [
TableCell(child: Text('id')),
TableCell(child: Text('姓名')),
TableCell(child: Text('年龄')),
TableCell(child: Text('性别')),
]),
..._list.map((user) {
return TableRow(children: [
TableCell(child: Text('${user.id}')),
TableCell(child: Text('${user.name}')),
TableCell(child: Text('${user.age}')),
TableCell(child: Text(user.sex == 0 ? '男' : '女')),
]);
}).toList()
],
)
],
),
);
}
}
根据条件查询,比如查询年龄为12的数据:
Future<List<User>> find(int age) async {
var _db = await db;
List<Map<String, dynamic>> result =
await _db.query('User', where: 'age = ?', whereArgs: [age]);
return result.isNotEmpty ? result.map((e) {
return User.fromJson(e);
}).toList():[];
}
使用:
_loadData() async {
_list = await DBProvider().find(12);
setState(() {});
}
更新数据
根据 User id 更新数据:
Future<int> update(User user) async {
var _db = await db;
return await _db
.update('User', user.toJson(), where: 'id = ?', whereArgs: [user.id]);
}
跳转到修改数据页面:
RaisedButton(
child: Text('修改第一行数据'),
onPressed: () {
if (_list.length > 1) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return _AddUser(
user: _list[0],
);
}));
}
},
),
保存修改的数据:
RaisedButton(
child: Text('保存'),
onPressed: () async {
var user = User(
name: '${_nameController.text}',
age: int.parse('${_ageController.text}'),
sex: int.parse('$_radioGroupValue'));
if (widget.user == null) {
_saveData(context,user);
} else {
_updateData(context,user);
}
},
)
_updateData(BuildContext context,User user) async {
user.id = widget.user.id;
int result = await DBProvider().update(user);
if (result > 0) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('修改数据成功,result:$result'),
));
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('修改数据失败,result:$result'),
));
}
}
删除数据
根据 id 删除符合条件的数据:
Future<int> delete(int id) async {
var _db = await db;
return await _db.delete('User', where: 'id = ?', whereArgs: [id]);
}
删除第一行数据,删除成功后刷新数据:
RaisedButton(
child: Text('删除第一行数据'),
onPressed: () async {
if (_list.length > 0) {
await DBProvider().delete(_list[0].id);
_loadData();
}
},
),
删除全部数据:
Future<int> deleteAll() async {
var _db = await db;
return await _db.delete('User');
}
总结
我们介绍了 SQLite 的基本用法,数据的增删改查是使用频率最高的,SQLite 还有一些高级的查询语句,比如分组、联合查询等,这些用到的频率不多。
SQLite 创建成功后会在本地创建一个 db_name.db 的文件,文件的目录就是初始化数据库时设置的目录。
- 如何通过TTL调试光猫
- 基于树莓派和Tensowflow的物体识别-brain
- SNA中:中心度及中心势诠释(不完整代码)
- 教程 | 基于计算机视觉使用Python和OpenCV计算道路交通
- 干货 | MVP模式在携程酒店的应用和扩展
- memlock过低导致的数据库性能问题(r6笔记第10天)
- OpenCV和SVM分类器在自动驾驶中的车辆检测
- 自动驾驶的模型预测控制
- 【专业技术】使用html5的十大原因
- 第五课:推理结果的可视化
- 第四课:模型的使用
- 【Java概念学习】--数组的初始化
- linux下重命名文件或文件夹使用mv既可实现。
- 第三课:把tensorflow,模型和测试数据导入Android工程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python入门教程笔记(二)控制语句
- Python入门教程笔记(一)简介、变量及基本数据类型
- C#知识点讲解之C#delegate、event、Action、EventHandler的使用和区别
- typescript高级用法之infer的理解与使用
- 基于业务场景下的图片/文件上传方案总结
- LeetCode96|二叉搜索树中的搜索
- LeetCode95|字符串中的第一个唯一字符
- LeetCode94|Pow(x,n)
- LeetCode93|数值的整数次方
- LeetCode92|排序数组
- 缓存 | 从本地缓存到分布式缓存, Guava, Caffeine, Memcached, Redis
- WebView三问—B站真题
- C++核心准则T.140:为所有可能重用的操作命名
- Service三问
- 事件分发机制三问