前一篇簡單介紹了SQLite與Flutter提供的支援package: sqflite,今天我們就來在我們的應用程式實際操作看看吧!
推薦給大家一個線上的SQLite編輯器:https://sqliteonline.com/ 可能有些人的電腦與筆者相同,無法直接開啟並閱讀資料庫檔案(.db),或者是不確定自己SQL語法是否正確,想先試跑一次再正式放進程式之中。那麼便可以使用這個線上的SQLite編輯器,除了可以開啟、編輯、儲存資料庫檔案,也可以將檔案視覺化,非常便利!
在這部份會用到幾個函式庫,因此記得先加入pubspec.yaml
並下載packages,在編寫程式時會一直跳出煩人的錯誤通知。
// pubspec.yaml
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
sqflite: ^2.3.3
path: ^1.9.0 // using join() https://pub.dev/packages/path
在正式開始我們的Coding之前,先讓筆者我簡單介紹一下這個接下來要做的資料庫是用來存什麼內容的。關於這個程式詳細的說明可參考【Day 09】的文章,而今天要處理的這個資料庫就是用來將使用者建立的行程儲存至裝置中。因此,我們的table最終將長像如此:
id | 名稱 | 類別 | 花費時間 | deadline | 備註 |
---|---|---|---|---|---|
1 | Ch1 | 化學 | 1h00min | 2024/10/02 | random text 1 |
2 | Ch3 | 數學 | 2h00min | 2024/10/01 | random text 2 |
3 | Ch2 | 國文 | 1h30min | 2024/10/04 | random text 3 |
在這個程式中,有兩個筆者建立的class與本篇主題相關,分別是Block
與BlocksDb
,接下來就讓我一一介紹他們的程式碼是在做什麼的吧。
因為這次是第一次實作,因此本篇中先暫時不會在資料庫及各個class加入時間與日期的資料型態與其處理方式。
這個是一個方塊Block,也是應用程式使用者所建立的行程。以下為它的結構:
class Block {
final String name;
final String? category, notes;
int? id;
Block(
{required this.name,
this.id = null,
this.category = null,
this.notes = null});
Map<String, Object?> toMap() {
return {
'id': id,
'name': name,
'category': category,
'notes': notes,
};
}
}
Block包含了[名稱name], [類別category], [備註notes]和[編號id]。其中,僅有名稱是建立block時必須設定的項目,類別與備註可以留空,而編號則是於加入資料庫時設定。
在sqflite中,要將一筆資料加入資料庫需要先將資料型態轉為Map<String, Object?>
,因此Block有著toMap()
這個函數來將它的資料全轉換為map。
這部份便是本篇的重點,是這個應用程式中專門用於處理blocks與資料庫的class。先來看看它的結構是如何吧。
class BlocksDb {
String dbFilename, tableName;
bool dbIsOpen;
late final db;
BlocksDb(
{required this.dbFilename,
required this.tableName,
this.dbIsOpen = false});
Future open() async {...}
Future<Block> insert(Block aBlock) async {...}
Future<int> delete(int id) async {...}
Future<int> update(Block aBlock) async {...}
Future close() async {...}
}
BlocksDb包含了以下幾個變數:[資料庫檔案名dbFilename], [表格名稱tableName], [資料庫是否已經開啟dbIsOpen], [資料庫db]。考量到未來程式中可能會使用此class來建立複數個資料庫或表格,因此將資料庫檔案明與表格名稱設成變數,可根據應用程式需求做調整。dbIsOpen
是一個用於判斷資料庫檔案是否已經被開啟並可以做編寫的一個布林值。若欲編寫時此變數為false,便會使用open()
開啟資料庫檔案,並將值設為true,再繼續原本的工作。而在最後的close()
,則會將dbIsOpen設為false,以表示檔案已關閉。
這個函式 open()
是用於開啟資料庫檔案的,來看一下程式是怎麼做的吧。
Future open() async {
db = openDatabase(
join(await getDatabasesPath(), dbFilename),
version: 1,
onCreate: (db, version) async {
return await db.execute('''CREATE TABLE $tableName (
id INTEGER PRIMARY KEY,
name TEXT, category TEXT,
notes TEXT)''');
},
);
dbIsOpen = true;
}
openDatabase()
是用於開啟資料庫檔案並讓我們之後可以做資料的存取。其中最重要的參數:檔案位置與名稱,是用getDatabasesPath()
獲得運作裝置通常資料儲存的資料夾,並用join()
來加上檔案名稱。在此筆者將openDatabase回傳的檔案命名為db
(database)。
除了設定檔案位置,也可以使用version
設定檔案的版本,用於之後的資料庫升級/降級。
除此以外,我們可以利用openDatabase的onCreate
來在初次建立資料庫時執行一些內容,在此,我們希望在建立新資料庫時能夠一併建立一個新的表格,而這個我們可以藉由db.execute('SQL Code')
來執行SQL的CREATE
指令。
若有需求,openDatabase也能夠在檔案開啟時、版本變動時、檔案設定時執行特定指令。詳細說明請點此處。
Future<Block> insert(Block aBlock) async {
if (!dbIsOpen) open();
aBlock.id = await db.insert(tableName, aBlock.toMap());
return aBlock;
}
首先,在加入新資料前先確認資料庫是否已經開啟。
db.insert可將新的資料插入資料庫db之中。它主要需要兩個參數,表格名稱以及map型態的資料。表格名稱便是在open時所CREATE的表格,而剛剛建立class Block的toMap()則在這裡派上用場了。
db.insert將會回傳資料被存進的列數,也就是編號id。
Future<int> delete(int id) async {
if (!dbIsOpen) open();
return await db.delete(tableName, where: 'id = ?', whereArgs: [id]);
}
Future<int> update(Block aBlock) async {
if (!dbIsOpen) open();
return await db.update(tableName, aBlock.toMap(),
where: 'id = ?', whereArgs: [aBlock.id]);
}
delete與update的方式非常接近,update僅僅是多了更新後資料的內容。除了表格名稱,還需要設定一個東西叫做where
。where [column_tag] = [column_name]
是SQL中的語法,等號左邊是行的名稱,右邊是要在那行尋找的內容。因為id具有獨特性,不會有重複的資料,因此在此選用id來在資料庫中尋找要編輯的資料。
Future close() async {
dbIsOpen = false;
return await db.close();
}
為了保證資料庫編寫完成後能夠正確的關閉,我們需要建立一個close的函式,並將dbIsOpen設為false以防關閉後再次嘗試編寫資料庫而發生錯誤。
實在是非常抱歉,沒能夠如期完成今天這篇文章,明天將會回來再將本文補齊的m(_ _)m
更新:
文章已經補完了,如果先前的版本有造成閱讀上的不便敬請見諒。未來會進可能早點完成文章的,別再拖到最後一刻了(☍﹏⁰。)
如果有任何想說的,歡迎留言及email~
順帶一提,因為本人的新手任務沒有做完,所以暫時無法回覆留言,不過會嘗試盡快完成並回覆所有訊息的!
email: nnyjan02426@gmail.com
Schedrag: nnyjan02426/Schedrag