老孟导读:上一篇文章讲解了 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 的文件,文件的目录就是初始化数据库时设置的目录。

交流

交流

老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

【Flutter 实战】大量复杂数据持久化的更多相关文章

  1. .Net Redis实战——事务和数据持久化

    Redis事务 Redis事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令,和关系数据库那种可以在执行的过程中进行回滚(rollback)的事务不同,在Redis里面,被MULTI命令和E ...

  2. Flutter数据持久化入门以及与Web开发的对比

    对于大部分安卓或者IOS开发人员来说,App的数据持久化可能是很平常的一个话题.但是对于Web开发人员来说,可能紧紧意味着localStorage和sessionStorage. Web开发 loca ...

  3. Kafka实战-数据持久化

    1.概述 经过前面Kafka实战系列的学习,我们通过学习<Kafka实战-入门>了解Kafka的应用场景和基本原理,<Kafka实战-Kafka Cluster>一文给大家分享 ...

  4. Docker数据持久化及实战(Nginx+Spring Boot项目+MySQL)

    Docker数据持久化: Volume: (1)创建mysql数据库的container docker run -d --name mysql01 -e MYSQL_ROOT_PASSWORD= my ...

  5. iOS开发——项目实战总结&数据持久化分析

    数据持久化分析 plist文件(属性列表) preference(偏好设置) NSKeyedArchiver(归档) SQLite 3 CoreData 当存储大块数据时你会怎么做? 你有很多选择,比 ...

  6. React Native 之 数据持久化

    前言 因为 实战项目系列 涉及到数据持久化,这边就来补充一下. 如本文有错或理解偏差欢迎联系我,会尽快改正更新! 如有什么问题,也可直接通过邮箱 277511806@qq.com 联系我. demo链 ...

  7. Redis数据持久化

    持久化选项 Redis提供了两种不同的持久化方法来将数据存储到硬盘里面.一种方法叫快照(snapshotting),它可以将存在于某一时刻的所有数据都写入硬盘里面.另一种方法叫只追加文件(append ...

  8. 《Flutter实战》开源电子书

    <Flutter实战>开源电子书 <Flutter实战> 开源了,本书为 Flutter中文网开源电子书项目,本书系统介绍了Flutter技术的各个方面,本书属于原创书籍(并非 ...

  9. Docker深入浅出系列 | 容器数据持久化

    Docker深入浅出系列 | 容器数据持久化 Docker已经上市很多年,不是什么新鲜事物了,很多企业或者开发同学以前也不多不少有所接触,但是有实操经验的人不多,本系列教程主要偏重实战,尽量讲干货,会 ...

  10. 【Flutter实战】移动技术发展史

    老孟导读:大家好,这是[Flutter实战]系列文章的第一篇,这并不是一篇Flutter技术文章,而是介绍智能手机操作系统.跨平台技术的演进以及我对各种跨平台技术看法的文章. 智能手机操作系统 塞班( ...

随机推荐

  1. JavaScript的数据类型判断学习理解和数据处理

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. SpringBoot整合SpringAdmin搭建监控平台

    在SpringBoot整合Actuator进行健康监控中,胜金讲述了通过Actuator进行健康监控,但是学习API并根据API开发前端需要花费相当大的精力,本次胜金就写一下通过SpringAdmin ...

  3. 一文读懂BeanFactory和FactoryBean区别

    一直以来,很多人对于Spring中的BeanFactory和FactoryBean都是分不清楚的 BeanFactory 这个其实是所有Spring Bean的容器根接口,给Spring 的容器定义一 ...

  4. 问卷星的数据导入spss后变量乱码如何处理?

    一般是字符编码问题.打开一个空的SPSS数据集,选择[编辑]-[选项]-[常规]-[数据和语法的字符编码].修改下当前的编码系统,原来是第一种就换成第二种,原来是第二种就换成第一种,打开一个数据再看看 ...

  5. Android开发之最火的开源框架之一Xutils2详解(摘自开源作者官方介绍详解)

    此框架说实话还是挺不错的,挺好用的,功能多,所以我也用过. 由于CSDN博客写的字数有限制,所以全文的用法打包成了markdown 文件,因为markdown真的太还用了. 全文下载地址为: http ...

  6. 虚拟机安装:VirtualBox上安装Ubuntu

    在Win10 中使用VirtualBox安装Ubuntu 本教程将指引读者在Windows10操作系统下使用开源虚拟机软件VirtualBox安装Ubuntu,读者也可以使用其他虚拟机VMware W ...

  7. MPL心得

    1.右值引用变量是个左值,把一个右值引用参数继续传递给其他函数调用时,需要使用std::forward否则会按照左值匹配 2.const T和T const在匹配模板参数的时候是相同的,而const ...

  8. Lua 调用的 C 函数保存 state 的两种方式: Storing State in C Functions 笔记

    http://yanbin.is-programmer.com/posts/94214.html Registery的Key 1. 整数Key用于Lua的引用机制,所以不要使用整数作为Key 2. 通 ...

  9. 公共项目开发:我为什么用 JSDoc,而不用 ts

    公共模块,通常会被多个项目.不同的开发人员使用,所以开发公共模块时,你自己会用还不够,要让所有人都能很快的知道怎么去使用,这一点很关键.通常会从3个方面做到这点: 精心分割代码逻辑,遵循开闭原则: 变 ...

  10. maven踩过的坑

    maven配置 maven默认配置 解决每次打开idea的spring项目都需要重新配置maven,选择file/other settings/preference for new projects ...