Flutter 数据库: 本地数据存储和状态管理的优雅结合
效果展示

说明:通过本地数据库存储数据,将你的汽车 ? 存入本地,并且通过状态管理刷新页面,可以控制车辆的启动状态(读写数据库)
前言 ?
最近由于业务需求,我开始研究Flutter
中的本地数据存储,要做好本地的数据存储最重要的就是对数据库的要有基本的了解,建库、建表、增删改查,都是数据库基本操作,各位在开始前一定要对数据库有一定的了解,不然会看的云里雾里? 。
小说明 ?
其实单论数据库本地的存储,粗略一想会觉得~很简单啊? ,没什么可以学习的点。实际上手代码写一个demo
也很简单,你可以轻松做到建库等一系列操作,但是本文要做的是将这些看似简单的操作和实际业务相结合,具体去扣其中的细节,相信看完还是会有不少收获的?。
技术选型
我们在Flutter
中操作数据库时,我们的首要目的一定是我们要对一些数据(具有一定数据量,非轻量级)的数据进行一些操作,那我们操作的目的是什么?一定是满足用户的需求,进行各类的数据展示? !既然如此,如何将我们数据更优雅的展示呢?或者说,如何在操作数据库时尽可能保证Flutter
的渲染性能?这离不开Flutter
里老生常谈的内容——状态管理。状态管理的方案有很多,我个人在学习这部分内容的时候看到很多采用bloc
的方式,但我的业务项目在前期已经采用了provider
,那咋办馁 ? ,那就只能自己研究了嘛~。明确了我的状态管理方案之后,就是挑选plugin
了。provider
作为官方插件,优先级自然不用说。那数据库呢?综合考虑后我们的数据库采用SQLite
,有人会问了,综合考虑是考虑了什么?? ,那还用问吗?

高热度➕ 高Likes
➕ Flutter Favorite
。口水不争气的从嘴里流了出来,太香了呀 ?
database
(数据库):SQLite
——sqflite
- 状态管理:
provider
——provider
开始构建、步步为营
第一步:准备充分 ?
我们首先要搭建一个新工程,在新工程了我们加入我们项目中所要用的各个三方库:
sqflite: ^1.3.1
provider: ^4.3.2
path_provider: ^1.6.11
复制代码
path_provider
:用于获取本地的储存路径
第二步:由简入繁 ?
首先第一步我们看到这样一个工程,而且我们要存数据库,那首先需要的是什么?是一个数据结构,用于我们的读
和写
两个操作,这个数据结构就是我们 ? 的数据结构。我们要有的各个属性:车的品牌,车的型号,是否启动。还有一个id
,方便我们的读取,其实熟悉数据库的同学应该也能想到,这个id
,就是我们之后的主键 ? 。
class Car {
int id;
String brand;
String type;
bool start;
Car({@required this.brand, this.type, this.start = false});
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (id != null) {
map['id'] = id;
}
map['brand'] = brand;
map['type'] = type;
map['start'] = start == true ? 1 : 0;
return map;
}
Car.fromMapObject(Map<String, dynamic> map) {
this.id = map['id'];
this.brand = map['brand'];
this.type = map['type'];
this.start = map['start'] == 1 ? true : false;
}
}
复制代码
第三步:关键的数据库操作 ??♂️
接下来的一步非常关键,迎合我们今天的主题。我们对数据库的操作包括建库、建表、增删改查,这些基础操作,我们都去UI层写会让我们的结构看起来非常乱 ? ,而且复用性非常差,所以我们这里抽一个单独的类出来,进行封装,我叫他DatabaseHelper
。得益于我们强大的插件,我们只需要处理上层API
即可 ? ,底层的多端操作已经被封装。
void _createDb(Database db, int newVersion) async {
await db.execute(
'CREATE TABLE $carsTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colBrand TEXT, $colType TEXT, $colStart INTEGER)');
}
// 读取数据
Future<List<Map<String, dynamic>>> getCarMapList() async {
Database db = await this.database;
var result = await db.query(carsTable);
return result;
}
// 增加数据
Future<int> insertCar(Car car) async {
Database db = await this.database;
var result = await db.insert(carsTable, car.toMap());
return result;
}
// 刷新数据
Future<int> updateCar(Car car) async {
Database db = await this.database;
var result = await db.update(carsTable, car.toMap(),
where: '$colId = ?', whereArgs: [car.id]);
return result;
}
// 删除数据
Future<int> deleteCar(int id) async {
Database db = await this.database;
int result =
await db.rawDelete('DELETE FROM $carsTable WHERE $colId = $id');
return result;
}
复制代码
第四步:同样关键的状态管理 ?
这一步也是我们的核心,我们如何将我们的数据库中的数据和UI部分相结合并且合理控制渲染范围 ?,听起来很复杂的样子,我们可以思考几个核心点,一点一点组成我们的这个部分 ⚒ 。
- 首先,选择了
provider
作为状态管理,我们根据传统要将我们封装的provider
类混入ChangeNotifier
。 - 我们需要多个状态改变的方法,每一个方法中都要通知刷新,即加入
notifyListeners()
方法。 - 我们要明确我们需要哪些方法:增加数据,修改数据,减少数据,读取数据。 相信带着上面这几个点,你看下面这段代码会有思路的多 ? :
class CarsProvider with ChangeNotifier {
DatabaseHelper _databaseHelper = DatabaseHelper();
List<Car> _cars;
int _count = 0;
UnmodifiableListView<Car> get allCars => UnmodifiableListView(_cars);
UnmodifiableListView<Car> get unStartedCars =>
UnmodifiableListView(_cars.where((car) => car.start == false));
UnmodifiableListView<Car> get startedCars =>
UnmodifiableListView(_cars.where((car) => car.start == true));
void getCarList() {
final Future<Database> dbFuture = _databaseHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Car>> carListFuture = _databaseHelper.getCarList();
carListFuture.then((carList) {
this._cars = carList;
this._count = carList.length;
notifyListeners();
});
});
}
int get count => _count;
void addCar(Car car) async {
int result;
if (car.id != null) {
result = await _databaseHelper.updateCar(car);
} else {
result = await _databaseHelper.insertCar(car);
}
if (result != 0) {
print('Success');
} else {
print('Failed');
}
notifyListeners();
}
void startCar(BuildContext context, Car car) async {
car.start = !car.start;
int result = await _databaseHelper.updateCar(car);
if (result != 0 && car.start == true) {
_showSnackBar(context, '${car.brand}' +' '+'${car.type}' + ' ' +'Start');
}
notifyListeners();
}
void deleteCar(BuildContext context, Car car) async {
if (car.id == null) {
_showSnackBar(context, 'No Car was deleted');
return;
}
int result = await _databaseHelper.deleteCar(car.id);
if (result != 0) {
_showSnackBar(context, 'Car Deleted Successfully');
} else {
_showSnackBar(context, 'Error Occurred while deleting note');
}
notifyListeners();
}
void _showSnackBar(BuildContext context, String message) {
final snackBar = SnackBar(
duration: Duration(milliseconds: 500),
content: Text(
message,
style: TextStyle(color: Colors.white),
),
backgroundColor: Theme.of(context).primaryColor,
);
Scaffold.of(context).showSnackBar(snackBar);
}
}
复制代码
第五步:丰满UI内容 ?
其实以上两个部分是我们的核心,如果能理解上面两个核心的类,我再抛出一些UI级的操作你应该会很好理解 ? 。 这里我就展示一个增加车辆的操作。更多内容可以查看文末的源码。
//新增一辆车
void onAdd() {
if (carBrandController.text.isNotEmpty && carTypeController.text.isNotEmpty ) {
final Car car = Car(brand: carBrandController.text, type:carTypeController.text, start: started);
Provider.of<CarsProvider>(context, listen: false).addCar(car);
Navigator.pop(context);
}
}
作者:多肉葡萄五分糖
链接:https://juejin.im/post/6863236259335766024
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。