版權聲明:本文為本人原創(chuàng)文章家浇,未經(jīng)本人允許不得轉(zhuǎn)載。
Hello碴裙,大家好這兩天利用空余時間忙著寫統(tǒng)計分析頁面蓝谨,所以隔了兩天來繼續(xù)更新。
第五篇為止我們基本把要展現(xiàn)的頁面和UI都寫完了青团,但是數(shù)據(jù)都是初始化好的譬巫,如何通過用戶自己添加一條數(shù)據(jù)并進行數(shù)據(jù)永久保存呢,有請今天的主角:sqflite督笆,F(xiàn)lutter的嵌入式數(shù)據(jù)庫插件芦昔。
要使用他我們首先要在pubspec.yaml
中引入這個包,同時由于數(shù)據(jù)庫操作要用到本地的路徑訪問娃肿,所以我們首先一并添加這些包:
dependencies:
flutter:
sdk: flutter
#數(shù)據(jù)庫
sqflite: ^0.11.0
#文件路徑訪問
path_provider: ^0.4.0
然后我們執(zhí)行get packages咕缎,獲取插件內(nèi)容珠十。下面我們正式開始今天的內(nèi)容。
一凭豪、新建數(shù)據(jù)庫及其增刪改查方法
1焙蹭、修改diy_project.dart
,添加如下代碼:
//根據(jù)項目的屬性內(nèi)容生成一個鍵值對map嫂伞,這個mao的鍵就是后面數(shù)據(jù)庫的字段孔厉,他們是一一對應的。
Map<String, dynamic> toMap() {
Map map = <String, dynamic>{
'name': _name,
'date': _date,
'place': _place,
'contact': _contact,
'imagePath': _imagePath,
'singlePrice': _singlePrice,
'nums': _nums,
'totalAmount': _totalAmount,
'itemCost': _itemCost,
'laborCost': _laborCost,
'profit': _profit,
'isCheckOut': _isCheckOut == true ? 1 : 0
};
if (_id != null) {
map['id'] = _id;
}
return map;
}
//根據(jù)map獲得一個diyProject的實例
DiyProject.fromMap(Map<String, dynamic> map) {
_id = map['id'];
_name = map['name'];
_date = map['date'];
_place = map['place'];
_contact = map['contact'];
_imagePath = map['imagePath'];
_singlePrice = map['singlePrice'];
_nums = map['nums'];
_totalAmount = map['totalAmount'];
_itemCost = map['itemCost'];
_laborCost = map['laborCost'];
_profit = map['profit'];
_isCheckOut = map['isCheckOut'] == 1;
}
以上代碼分別是根據(jù)項目實例轉(zhuǎn)換成map和通過map獲得一個項目實例帖努。注意撰豺,其中map的鍵是和后面數(shù)據(jù)庫字段相對應的。
2拼余、新建數(shù)據(jù)庫
在model目錄下新建data_base.dart 文件污桦,我們將在里面編寫數(shù)據(jù)庫相關的代碼。
首先導入插件的引用:
import 'package:activity_record/model/diy_project.dart'; //項目類匙监,供數(shù)據(jù)庫操作使用
import 'package:sqflite/sqflite.dart'; //數(shù)據(jù)庫
import 'dart:io'; //本地文件使用
import 'dart:async';//異步操作
import 'package:path/path.dart';//本地路徑訪問
import 'package:path_provider/path_provider.dart';//本地路徑訪問
繼續(xù)新建數(shù)據(jù)庫類
class DataBase {
Database _myDateBase;
//定義表名
final String tableName = "diyTable1";
//定義各個字段名
final String columnId = "id";
final String columnName = "name";
final String columnDate = "date";
final String columnPlace = "place";
final String columnContact = "contact";
final String columnImagePath = "imagePath";
final String columnSinglePrice = "singlePrice";
final String columnNums = "nums";
final String columnTotalAmount = "totalAmount";
final String columnItemCost = "itemCost";
final String columnLaborCost = "laborCost";
final String columnProfit = "profit";
final String columnIsCheckOut = "isCheckOut";
//獲取數(shù)據(jù)庫
Future get db async {
if (_myDateBase != null) {
print('數(shù)據(jù)庫已存在');
return _myDateBase;
} else
_myDateBase = await initDb();
return _myDateBase;
}
//初始化數(shù)據(jù)庫凡橱,根據(jù)路徑版本號新建數(shù)據(jù)庫
initDb() async {
Directory directory = await getApplicationDocumentsDirectory();
String path = join(directory.path, "diyDB.db");
var dataBase = await openDatabase(path, version: 1, onCreate: _onCreate);
print('數(shù)據(jù)庫創(chuàng)建成功,version: 1');
return dataBase;
}
//新建數(shù)據(jù)庫表
FutureOr _onCreate(Database db, int version) async {
await db.execute('''create table $tableName(
$columnId integer primary key autoincrement,
$columnName text not null,
$columnDate text,
$columnPlace text,
$columnContact text,
$columnImagePath integer ,
$columnSinglePrice integer not null,
$columnNums integer not null,
$columnTotalAmount integer not null,
$columnItemCost integer ,
$columnLaborCost integer ,
$columnProfit integer not null,
$columnIsCheckOut integer not null)''');
print('Table is created');
}
}
以上分別說明數(shù)據(jù)庫是如何建立起來的:
1亭姥、定義表明和表的字段名梭纹,因為我這個項目屬性不多就用了一張表,注意這個字段名要和之前的diyProject里的map方法里的鍵要一致致份,因為后面的數(shù)據(jù)操作都要依賴這個变抽。
2、(1)初始化數(shù)據(jù)庫氮块,首先通過getApplicationDocumentsDirectory();獲得一個本地目錄用于存放數(shù)據(jù)庫文件绍载,
(2)然后通過join(directory.path, "diyDB.db")在某個目錄里新建一個數(shù)據(jù)庫文件,其中括號內(nèi)屬性是目錄路徑和數(shù)據(jù)庫名稱滔蝉,同時返回這個數(shù)據(jù)庫文件的路徑击儡。
(3)最后通過這個路徑我們打開數(shù)據(jù)庫并初始化數(shù)據(jù)庫,包括數(shù)據(jù)庫版本和新建數(shù)據(jù)表
3蝠引、獲得這個數(shù)據(jù)庫實例阳谍,首先判斷數(shù)據(jù)庫是否存在,如果不存在則新建螃概,存在則返回矫夯。
回到home_page.dart
添加:
//實例化數(shù)據(jù)庫對象
DataBase _database = new DataBase();
運行一下程序看看,如果是首次運行應該會在控制臺看到這樣的話:
當你下次再運行應用的時候就會再控制臺看到提示 ‘數(shù)據(jù)庫已存在’吊洼,除非卸載應用重新安裝训貌,否則數(shù)據(jù)庫文件將一直存在。
3、增加數(shù)據(jù)庫增刪改查方法
數(shù)據(jù)庫已經(jīng)有了递沪,顯然我們是要對數(shù)據(jù)庫進行操作的豺鼻,雖然數(shù)據(jù)庫感覺很復雜的樣子,但是歸納起來無非就是增刪改查款慨,數(shù)據(jù)庫操作會涉及數(shù)據(jù)庫sql語句儒飒,如果這塊沒有基礎的話我建議去了解下sql語句基礎,對普通的增刪改查有個了解檩奠,知道怎么寫即可桩了。
繼續(xù)在數(shù)據(jù)庫類里添加以下代碼:
1、新增數(shù)據(jù)
//插入diyProject
Future<int> insertDiyProject(DiyProject diy) async {
//獲取數(shù)據(jù)庫實例
Database database = await db;
//diy.toMap()是將diy實例轉(zhuǎn)換成字段名和值對應的map
var result = database.insert(tableName, diy.toMap());
print('數(shù)據(jù)已插入');
return result;
}
2笆凌、查詢數(shù)據(jù)
//獲取所有diyProject
Future<List> getDiyProjects() async {
//獲取數(shù)據(jù)庫實例
Database database = await db;
//返回一個 map型的數(shù)組圣猎,其中map是由字段名和值構(gòu)成
var result = await database
.rawQuery("select * from $tableName order by $columnId desc");
print('獲取所有diyProject,當前diyProject有: $result');
return result;
}
//獲取所有未結(jié)賬的diyProject
Future<List> getUnCheckedDiyProjects() async {
Database database = await db;
var result = await database.rawQuery(
"select * from $tableName where $columnIsCheckOut = 0 order by $columnId desc");
return result;
}
//獲取diyProject總數(shù)
Future<int> getDiyCount() async {
Database database = await db;
var result = await database.rawQuery("select count(*) from $tableName");
/*查詢結(jié)果返回的是一個map類型的數(shù)組士葫,雖然這里查詢結(jié)果只有一條乞而,但是很多查詢是會返回多條數(shù)據(jù)的,所以是一個數(shù)組類型慢显。
這里我們?nèi)?shù)組的第一個值爪模,然后再通過鍵來取對應的數(shù)據(jù)
*/
return result[0]['count(*)'];
}
//獲取單個diyProject
Future<DiyProject> getDiyProject(int id) async {
Database database = await db;
//根據(jù)id查詢對應的diy項目,并返回一個map類型的數(shù)組
var result = await database
.rawQuery("select * from $tableName where $columnId = $id");
if (result.length == 0) {
return null;
} else
return DiyProject.fromMap(result[0]);
}
我這里一共寫了4個查詢方法荚藻,注釋里已經(jīng)分別寫了查詢的內(nèi)容屋灌,都是后面會用到的。
注意點:數(shù)據(jù)庫查詢操作返回的都是map類型的數(shù)組应狱,map中的鍵就是查詢對象共郭,值就是查詢的結(jié)果。哪怕查詢結(jié)果只有一條返回的也是一個數(shù)組疾呻。
3除嘹、更新數(shù)據(jù)
//更新diyProject
Future<int> updateDiyProject(DiyProject diyProject) async {
Database database = await db;
var result = database.update(tableName, diyProject.toMap(),
where: "$columnId = ?", whereArgs: [diyProject.id]);
print('我是更新數(shù)據(jù)的方法 本次更新的res: $result');
return result;
}
4、刪除數(shù)據(jù)
//刪除diyProject
Future<int> deleteDiyProject(int id) async {
Database database = await db;
var result = await database
.rawDelete("delete from $tableName where $columnId = $id");
return result;
}
4岸蜗、添加關閉數(shù)據(jù)庫方法
別忘了當不再使用的時候關閉數(shù)據(jù)庫
//關閉數(shù)據(jù)庫
Future close() async {
Database database = await db;
database.close();
}
到這里呢尉咕,數(shù)據(jù)庫類我們就完全寫好了,這樣我們就可以通過不同的方法來對數(shù)據(jù)庫進行讀寫數(shù)據(jù)啦璃岳∧甓校看不到效果可不行,當然要去業(yè)務邏輯里實現(xiàn)以下铃慷。
二单芜、實現(xiàn)用戶和數(shù)據(jù)庫的交互
在進行業(yè)務邏輯實現(xiàn)之前,我們先要合并一些文件犁柜。在之前的文章中缓溅,為了給大家演示清楚頁面框架結(jié)構(gòu)和里面控件的關系,我將框架和UI分開了赁温,但是因為在這個應用中頁面UI的內(nèi)容都會根據(jù)用戶輸入而改變坛怪,所以需要將框架和頁面UI放在一個dart文件里淤齐,這樣操作更方便。
我們需要分別合并以下文件:
1袜匿、ui目錄下的diy_add_show.dart 和pages目錄下的diy_add_dialog.dart合并更啄,所謂合并是指將ui文件里的控件內(nèi)容復制到pages頁面文件里的Scaffold所屬的body屬性里。
2居灯、ui目錄下的diy_info_show.dart和pages目錄下的diy_item_info.dart合并
3祭务、ui目錄下的diy_list_show.dart和pages目錄下的home_page.dart合并
要實現(xiàn)用戶新增數(shù)據(jù)到數(shù)據(jù)庫并展現(xiàn)在首頁列表中需要分以下幾個步驟:
1、通過用戶輸入的內(nèi)容獲得一個diyProject實例對象
2怪嫌、當用戶點擊保存按鈕的時候?qū)㈨椖繉嵗龑ο髢?nèi)容插到數(shù)據(jù)庫
3义锥、告訴首頁數(shù)據(jù)庫里新增了一條數(shù)據(jù)需要重繪UI以顯示新增的這條數(shù)據(jù)
通過以上分析我們可以基本得知在新增項目頁面,用戶主要分為兩類操作岩灭,在各個輸入框中填入項目信息和點擊保存拌倍。
那么我們首先在diy_add_dialog.dart中新增點擊保存后的方法:
diy_add_dialog.dart
//點擊保存按鈕進行數(shù)據(jù)保存入庫
_saveDiyItem(
String name,
String date,
String place,
String contact,
String imagePath,
int singlePrice,
int nums,
int totalAmount,
int itemCost,
int laborCost,
int profit,
bool isCheckOut) async {
DiyProject newDiyProject = new DiyProject(
name,
date,
place,
contact,
imagePath,
singlePrice,
nums,
totalAmount,
itemCost,
laborCost,
profit,
isCheckOut);
int savedID = await widget.db.insertDiyProject(newDiyProject);
print('插入的savedID:$savedID');
以上方法執(zhí)行了兩步操作:
1、根據(jù)參數(shù)實例化一個diyProject實例對象
2噪径、通過調(diào)用數(shù)據(jù)庫的insertDiyProject方法將對象插入數(shù)據(jù)庫
要在這個頁面對數(shù)據(jù)庫進行操作柱恤,所以需要將數(shù)據(jù)庫作為這個類的構(gòu)造函數(shù)的參數(shù),這樣當從首頁進入到這個頁面的時候?qū)?shù)據(jù)庫實例作為參數(shù)傳帶進來找爱。
所以在類的開頭作如下修改:
diy_add_dialog.dart
class DiyAddDialog extends StatefulWidget {
DiyAddDialog({Key key, this.db,}) : super(key: key);
var db;
@override
DiyAddDialogState createState() => new DiyAddDialogState();
}
保存的方法有了琼梆,剩下的就是在保存按鈕里添加這個方法碰辅,同時點擊后關閉添加頁面爹谭,回到首頁進行展示奏寨。
繼續(xù)修改diy_add_dialog.dart,首先在appbar里添加保存按鈕:
diy_add_dialog.dart
class DiyAddDialogState extends State<DiyAddDialog> {
...
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(_title),
actions: <Widget>[
new FlatButton(
child: new Text(
'保存',
style: Theme.of(context)
.textTheme
.subhead
.copyWith(color: Colors.white),
),
onPressed: () {},
)
],
),
body:
...
然后在onPressed: () {}的回調(diào)函數(shù)里添加保存數(shù)據(jù)的方法:
onPressed: () {
_saveDiyItem(
_nameTextEditingController.text,
DateFormat.yMd("en_US").format(_selectedDate),
_placeTextEditingController.text,
_contactTextEditingController.text,
_imagePath,
int.parse(_singlePriceTextEditingController.text),
int.parse(_numsTextEditingController.text),
_totalAmountCalculate(),
int.parse(_itemCostTextEditingController.text == ''
? '0'
: _itemCostTextEditingController.text),
int.parse(_laborCostTextEditingController.text == ''
? '0'
: _laborCostTextEditingController.text),
_profitCalculate(),
_isCheckedOut);
Navigator.of(context).pop();
},
以上代碼注意:
1吮播、_savedDiyItem()方法里的參數(shù)有通過用戶輸入獲取的变屁,也有通過方法計算得知的,比如_nameTextEditingController.text就是用戶輸入的名字薄料,.text是將監(jiān)聽獲得內(nèi)容轉(zhuǎn)換成文本敞贡。
2、而int.parse(_singlePriceTextEditingController.text),則是將文本輸入的金額轉(zhuǎn)換成int型摄职。
3誊役、像總金額、利潤等值不是通過用戶輸入的谷市,而是通過用戶輸入的內(nèi)容計算而來蛔垢,所以我們要添加兩個計算的方法如下:
//根據(jù)輸入計算總金額
_totalAmountCalculate() {
int _totalAmount = int.parse(_singlePriceTextEditingController.text) *
int.parse(_numsTextEditingController.text);
return _totalAmount;
}
//根據(jù)輸入計算利潤
_profitCalculate() {
int totalAmount = _totalAmountCalculate();
int profit = totalAmount -
int.parse(_itemCostTextEditingController.text == ''
? '0'
: _itemCostTextEditingController.text) -
int.parse(_laborCostTextEditingController.text == ''
? '0'
: _laborCostTextEditingController.text);
return profit;
}
以上兩段代碼中可能你都發(fā)現(xiàn)了三元運算符int.parse(_laborCostTextEditingController.text == '' ? '0' : _laborCostTextEditingController.text,
作用是當用戶沒有輸入的時候給他一個默認值0迫悠,否則插入數(shù)據(jù)庫會報錯鹏漆。
4、在onPressed回調(diào)函數(shù)最后添加Navigator.of(context).pop();表示點擊按鈕后就會將該頁面出棧,回到首頁艺玲。
說了這么多括蝠,是時候自己操作一把了,打開新增頁面饭聚,自己嘗試輸入內(nèi)容忌警,然后點擊保存:
控制臺會看到如下信息:
而且也回到首頁了,但是是不是一臉蒙蔽秒梳,因為首頁并沒有展示這條diy項目信息法绵,到底是怎么回事呢,因為我們并沒有告訴首頁展示的數(shù)據(jù)內(nèi)容發(fā)生了變化酪碘。下面我們回到home_page.dart朋譬,讓剛才添加的內(nèi)容展現(xiàn)出來。
修改
home_page.dart
如下添加展示內(nèi)容列表
//實例化diyProjects數(shù)組兴垦,并初始化空徙赢,作為首頁要展示的內(nèi)容列表
List<DiyProject> _diyProjects = <DiyProject>[];
每次運行程序的時候或者需要刷新的時候,我們需要先從數(shù)據(jù)庫遍歷數(shù)據(jù)滑进,并加到要展示的內(nèi)容列表犀忱,從而進行數(shù)據(jù)展示募谎,用到的就是之前數(shù)據(jù)庫類里的查詢方法:
//遍歷數(shù)據(jù)庫初始化_diyProjects數(shù)據(jù)
_getDiyProjects() async {
print('=========開始讀取數(shù)據(jù)庫全部數(shù)據(jù)=========');
List result = await _database.getDiyProjects();
_diyProjects.clear();
if (result.length != 0) {
setState(() {
result.forEach((diy) => _diyProjects.add(DiyProject.fromMap(diy)));
});
}
}
以上代碼首先通過數(shù)據(jù)庫查詢獲得一個map類型的數(shù)組扶关,然后通過diyProject的fromMap方法將這些查詢的結(jié)果轉(zhuǎn)化成一個個diyProject實例,然后添加到_diyProjects數(shù)組中数冬。并通過setState告訴應用要重繪頁面节槐。
查詢數(shù)據(jù)庫并告訴應用進行頁面重繪的方法是有了,那么何時執(zhí)行這個方法呢拐纱?铜异?
其實你想一想應該就知道:
1、每次打開應用的時候秸架,我們需要能看到數(shù)據(jù)庫已有的數(shù)據(jù)揍庄。
在初始化里添加這個遍歷數(shù)據(jù)的方法,這樣每次打開應用就會看到所有數(shù)據(jù)了东抹。
//數(shù)據(jù)初始化
@override
void initState() {
super.initState();
_getDiyProjects();
}
2蚂子、每次點擊新增然后添加完點擊保存的時候我們想看到新的數(shù)據(jù)。
注意這個時間點的描述是這樣的:點擊新增頁面的保存按鈕然后退回到首頁的時候缭黔。我們知道頁面路由的pop是可以返回數(shù)據(jù)的食茎,我們這里雖然不用返回數(shù)據(jù),但是我們就是要在知道返回回來的時候遍歷數(shù)據(jù)庫進行數(shù)據(jù)刷新馏谨。
所以我們需要在點擊新增按鈕的地方使用async異步延遲的方法來進到新頁面從而知曉何時從頁面返回别渔,從而進行數(shù)據(jù)刷新,修改floatingActionButton的onPressed回調(diào)函數(shù),通過異步方式進入新頁面并等待返回時刷新數(shù)據(jù)
floatingActionButton: new FloatingActionButton(
onPressed: () {
_addDiyProject();
},
child: new Icon(Icons.add),
),
//進入新增頁面
Future _addDiyProject() async {
await Navigator.of(context).push(new MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return new DiyAddDialog(
db: _database,
diyProjects: _diyProjects,
);
}));
//返回后哎媚,通過_getDiyProjects()方法重新遍歷數(shù)據(jù)庫并刷新頁面
_getDiyProjects();
}
這樣當我們點擊新增按鈕喇伯,通過輸入信息后點擊保存返回首頁就可以看到自己新增的card數(shù)據(jù)了,而且由于數(shù)據(jù)是保存在數(shù)據(jù)庫當中拨与,即使關閉應用重新打開之前添加的數(shù)據(jù)也都會在艘刚,主要不卸載數(shù)據(jù)都不會丟失。
可能你也有這樣的疑慮:能不能通過Navigator.of(context).pop直接返回這個新增的diyProject截珍,然后將接收到的返回的diyProject插入數(shù)組然后刷新頁面呢攀甚?當然是可以的,你嘗試著實現(xiàn)下看看岗喉。
留一個小作業(yè)給大家吧秋度,還記得在上一篇我在首頁添加了一個tabbar,分別進行所有項目和未結(jié)項目的展示钱床,我這里主要是針對所有項目進行了說明荚斯,如何實現(xiàn)未結(jié)項目的展示嘗試著做一下把。
今日總結(jié)
1查牌、數(shù)據(jù)庫sqflite的使用
2事期、通過獲取用戶輸入實例化對象并插入數(shù)據(jù)庫中
3、通過帶async的Navigator.of(context).push纸颜,得知何時返回兽泣,并執(zhí)行相應操作
4、通過setState() 告訴系統(tǒng)狀態(tài)發(fā)生變化胁孙,需要重新繪制頁面UI從而展示新的內(nèi)容
最后附上項目源碼地址:https://gitee.com/xusujun33/activity_record_jia.git
有不明白的或者有出入的可以對照查看唠倦,當然程序一直在更新,所以可能會有內(nèi)容上的出入涮较,但是方法都是相通的稠鼻。