【Flutter 實戰】大量複雜數據持久化

老孟導讀:上一篇文章講解了 Android 和 iOS 的文件目錄系統,此篇文章講解如何使用 SQLite 保存數據。
歡迎你們投稿http://laomengit.com/plan/Contribution.htmlhtml

保存數據到本地是應用程序很是重要的功能之一,好比以下場景:一個新聞類或者博客類的應用程序,打開後進入首頁,若是本地沒有保存數據,則須要經過網絡獲取數據,在返回數據以前,用戶看到的是空白頁面,而若是本地保存了部分新聞,則顯示這部分數據,等待最新的數據返回時在刷新便可,對用戶體驗來講,明顯第二種體驗更佳。git

SQLite 是目前最受歡迎的本地存儲框架之一,此篇文章將會介紹如何使用 SQLite 保存、查詢、刪除數據等。github

SQLite pub地址:https://pub.flutter-io.cn/packages/sqflitesql

SQLite Github:https://github.com/tekartik/sqflite數據庫

SQLite 講解:https://www.sqlitetutorial.net/json

若是您對數據庫和 SQL 語句相關知識有一點了解,下面的講解會比較容易理解,若是對數據庫和 SQL 語句相關知識一點不瞭解,那下面的講解理解起來可能會有必定的難度。微信

那是否須要先去學習數據庫和 SQL 語句相關知識?我以爲沒有必要,應用程序使用的數據庫和 SQL 語句相關知識都是很是基礎的,好比打開數據庫、建立表、SQL 查詢語句、更新語句、刪除語句等,這些都是固定的格式,固定語句記住便可。網絡

添加依賴

SQLite 並非 Flutter 系統自帶的,而是一個第三方插件,在項目的 pubspec.yaml 文件中添加依賴:app

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】:

相關文章
相關標籤/搜索