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

2020-08-2514:07:04APP与小程序开发Comments2,155 views字数 4807阅读模式

效果展示

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

说明:通过本地数据库存储数据,将你的汽车 ? 存入本地,并且通过状态管理刷新页面,可以控制车辆的启动状态(读写数据库)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

前言 ?

最近由于业务需求,我开始研究Flutter中的本地数据存储,要做好本地的数据存储最重要的就是对数据库的要有基本的了解,建库、建表、增删改查,都是数据库基本操作,各位在开始前一定要对数据库有一定的了解,不然会看的云里雾里? 。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

小说明 ?

其实单论数据库本地的存储,粗略一想会觉得~很简单啊? ,没什么可以学习的点。实际上手代码写一个demo也很简单,你可以轻松做到建库等一系列操作,但是本文要做的是将这些看似简单的操作和实际业务相结合,具体去扣其中的细节,相信看完还是会有不少收获的?。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

技术选型

我们在Flutter中操作数据库时,我们的首要目的一定是我们要对一些数据(具有一定数据量,非轻量级)的数据进行一些操作,那我们操作的目的是什么?一定是满足用户的需求,进行各类的数据展示? !既然如此,如何将我们数据更优雅的展示呢?或者说,如何在操作数据库时尽可能保证Flutter的渲染性能?这离不开Flutter里老生常谈的内容——状态管理。状态管理的方案有很多,我个人在学习这部分内容的时候看到很多采用bloc的方式,但我的业务项目在前期已经采用了provider,那咋办馁 ? ,那就只能自己研究了嘛~。明确了我的状态管理方案之后,就是挑选plugin了。provider作为官方插件,优先级自然不用说。那数据库呢?综合考虑后我们的数据库采用SQLite,有人会问了,综合考虑是考虑了什么?? ,那还用问吗?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

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

高热度➕ 高LikesFlutter Favorite。口水不争气的从嘴里流了出来,太香了呀 ?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

  • database(数据库):SQLite —— sqflite
  • 状态管理:provider —— provider

开始构建、步步为营

第一步:准备充分 ?

我们首先要搭建一个新工程,在新工程了我们加入我们项目中所要用的各个三方库:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

  sqflite: ^1.3.1
  provider: ^4.3.2
  path_provider: ^1.6.11
复制代码
  • path_provider:用于获取本地的储存路径

第二步:由简入繁 ?

首先第一步我们看到这样一个工程,而且我们要存数据库,那首先需要的是什么?是一个数据结构,用于我们的两个操作,这个数据结构就是我们 ? 的数据结构。我们要有的各个属性:车的品牌,车的型号,是否启动。还有一个id,方便我们的读取,其实熟悉数据库的同学应该也能想到,这个id,就是我们之后的主键 ? 。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

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即可 ? ,底层的多端操作已经被封装。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

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部分相结合并且合理控制渲染范围 ?,听起来很复杂的样子,我们可以思考几个核心点,一点一点组成我们的这个部分 ⚒ 。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

  • 首先,选择了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级的操作你应该会很好理解 ? 。 这里我就展示一个增加车辆的操作。更多内容可以查看文末的源码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

//新增一辆车
  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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/20119.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/xcx/20119.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定