1.前言
2.介面功能設計
3.程式實作: Score_History_DB.dart
今天的鐵人賽內容,將應用昨天文章: D10-輕巧強大SQLite: Flutter開發的秘密武器所學到的內容做改寫,撰寫具有基本資料庫功能的程式碼,包括:
這份 Score_History_DB.dart
程式碼提供以下功能和介面:
ScoreHistoryDBPage
widget ,該 widget 包含一個名稱為 "Score History" 的頂部導航欄(AppBar)。FutureBuilder
widget ,根據 _scoreHistoryDB.getAllScores()
方法返回的數據 Future 來構建分數歷史記錄的 UI。ScoreHistoryDB
類別來管理分數歷史記錄的資料庫操作。總結,這個 Flutter 應用程式提供了一個分數歷史記錄的介面,可以查看和管理分數記錄。它使用了 SQLite 資料庫來持久化存儲分數記錄,並在使用者介面來顯示這些測驗記錄。
Score_History_DB.dart
class ScoreHistoryDBPage extends StatefulWidget {}
class ScoreHistoryDBPage extends StatefulWidget {
@override
_ScoreHistoryDBPageState createState() => _ScoreHistoryDBPageState();
}
class _ScoreHistoryDBPageState extends State<ScoreHistoryDBPage> {}
Score History
。class _ScoreHistoryDBPageState extends State<ScoreHistoryDBPage> {
// 目的: 用來顯示分數歷史記錄的頁面
late ScoreHistoryDB _scoreHistoryDB;
@override
// 是一個生命週期方法,當這個狀態對象被創建時會自動調用
void initState() {
super.initState();
// 使用單一模式的實例 (Use singleton instance)
_scoreHistoryDB = ScoreHistoryDB
.instance; // _scoreHistoryDB 變數被初始化為 ScoreHistoryDB.instance
// 這表示使用單一模式的方式來獲取 ScoreHistoryDB 的實例,用於與分數歷史記錄資料庫進行互動
}
@override
// build() 方法是另一個生命週期方法,當 UI 需要被構建時會自動調用
Widget build(BuildContext context) {
return Scaffold(
// 在這個方法中,Scaffold widget 被返回,該 widget 包含了一個 AppBar 和 Body 區域。
appBar: AppBar(
// 應用程式的頂部導航欄,標題被設置為 "Score History"
title: Text('Score History'),
),
body: FutureBuilder<List<Map<String, dynamic>>>(
// body 區域是 FutureBuilder widget
// 它根據 _scoreHistoryDB.getAllScores() 方法返回的 Future 來構建 UI
future: _scoreHistoryDB.getAllScores(),
builder: (context, snapshot) {
// 在 FutureBuilder 的 builder 方法中,根據不同的 snapshot 狀態來構建不同的 UI
if (snapshot.connectionState == ConnectionState.waiting) {
// 如果連接狀態為等待中(ConnectionState.waiting)
return CircularProgressIndicator(); // 則顯示一個進度指示器
} else if (snapshot.hasData && snapshot.data!.length > 0) {
// 如果 snapshot 中有數據且數據長度大於 0 (表示有歷史紀錄)
final scoreList = snapshot.data!;
return ListView.builder(
// 則將數據解析並顯示在一個 ListView 中,每個分數記錄都作為 ListTile 顯示
itemCount: scoreList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('Score: ${scoreList[index]['totalScore']}'), // 取得測驗總分
subtitle:
Text('Date: ${scoreList[index]['measurementDate']}'), // 取得測驗日期
);
},
);
} else {
// 如果 snapshot 中沒有數據,則顯示文字 "No history records available."
return Text('No history records available.');
}
},
),
);
}
}
class ScoreHistoryDB {}
openDB()
insertScore()
getAllScores()
class ScoreHistoryDB {
// ScoreHistoryDB 是一個類別,用於管理分數歷史記錄的資料庫操作
static final ScoreHistoryDB _instance = ScoreHistoryDB
._privateConstructor(); // _instance 是一個靜態屬性,用於保存 ScoreHistoryDB 的單一實例
static Database? _database; // _database 是一個靜態屬性,用於保存資料庫實例
// 一個私有的單一模式(Singleton class)
// 確保只有在內部才能創建 ScoreHistoryDB 的實例
ScoreHistoryDB._privateConstructor();
// ScoreHistoryDB get instance 是一個靜態方法,用於獲取 ScoreHistoryDB 的單一實例
static ScoreHistoryDB get instance => _instance;
// database 是一個異步 getter 方法,用於獲取資料庫實例
Future<Database> get database async {
if (_database != null) return _database!; // 如果 _database 不為空,則直接返回
_database = await openDB(); // 否則打開資料庫
return _database!;
}
// openDB() 是一個異步方法,用於打開資料庫
Future<Database> openDB() async {
print("Opening database...");
_database = await openDatabase(
// 它在資料庫創建時執行一些初始化操作,例如創建分數記錄表
'score_history.db',
version: 1,
onCreate: (db, version) {
db.execute('''
CREATE TABLE IF NOT EXISTS scores (
id INTEGER PRIMARY KEY,
totalScore INTEGER,
measurementDate TEXT
)
''');
},
);
print("Database opened successfully");
return _database!;
}
// insertScore() 是一個異步方法,用於插入分數記錄到資料庫
// 這個函式返回一個 Future 物件,表示這個操作是一個非同步操作,並且不會返回任何值
// (int totalScore, String measurementDate):函式的兩個參數,分別是症狀總分數和測量日期。將這些值作為引數傳遞給函式
// await 關鍵字:在 Dart 中,用於等待非同步操作儲存的關鍵字
// 它將使程式碼等待資料庫插入操作儲存,然後再繼續執行後續的程式碼
// _database 是類中的一個初始化的資料庫連接
Future<void> insertScore(int totalScore, String measurementDate) async {
// print("Inserting score: $totalScore on ${measurementDate}");
// 'scores' 是資料表的名稱,'measurementDate = ?' 是查詢的條件
// 表示在 'scores' 表中搜尋 'SUBSTR(measurementDate, 1, 10) = ?' 值等於 whereArgs 變數的記錄
final existingRecord = await _database!.query(
'scores',
where: 'SUBSTR(measurementDate, 1, 10) = ?',
whereArgs: [
measurementDate.substring(0, 10)
], // 對於 SQLite 來說,日期的字串是以 0 為基底的索引
);
// print("existingRecord: $existingRecord"); // Debug: 列印變數內容
if (existingRecord.isNotEmpty) { // 如果結果不為空,表示資料庫中已經存在該日期的記錄
await _database!.update( // 如果存在相同日期的記錄,則使用 _database!.update() 方法來更新該日期的記錄
'scores',
{'totalScore': totalScore, 'measurementDate': measurementDate},
where: 'SUBSTR(measurementDate, 1, 10) = ?',
whereArgs: [measurementDate.substring(0, 10)],
);
print("Score updated successfully");
} else { // 如果不存在相同日期的記錄,則使用 _database!.insert() 方法來插入一條新的記錄
await _database!.insert( // 這行程式碼呼叫 _database 中的 insert 方法(由資料庫庫或框架提供的內建方法)
// 將一個新的分數紀錄插入到 'scores' 表中。這個表是儲存分數紀錄的地方。
// 'totalScore' 和 'measurementDate' 是列名,totalScore 和 measurementDate 則是你傳遞給函式的參數
'scores',
{'totalScore': totalScore, 'measurementDate': measurementDate.substring(0, 10)},
);
print("Score inserted successfully"); // 在終端機(Terminal)顯示插入資料成功的訊息
}
}
// getAllScores() 是一個異步方法,用於獲取所有分數記錄,按照 id 由大到小排列
// 實現在回傳結果中去除重複日期,並僅保留當天時間最新的記錄,循以下步驟:
// 1.使用 GROUP BY 來按日期分組 & 使用 MAX 函數找出每個日期分組中的最大時間,即當天最新的時間。
// 2.使用 JOIN 來連接原始資料表和包含最大時間的結果,以選取對應的記錄。
Future<List<Map<String, dynamic>>> getAllScores() async {
print("Fetching all scores...");
// 1.建立子查詢,找出每個日期分組中的最新時間 (id數值最大的那筆)
String subquery = '''
SELECT SUBSTR(measurementDate, 1, 10) AS date, MAX(id) AS lastId
FROM scores
GROUP BY date
''';
// 2.使用子查詢和 JOIN 來取得最新的記錄, 不能left join
String query = '''
SELECT s.*
FROM scores s
JOIN ($subquery) t ON SUBSTR(s.measurementDate, 1, 10) = t.date AND s.id = t.lastId
ORDER BY s.id DESC
''';
List<Map<String, dynamic>> result = await _database!.rawQuery(query);
print("Fetched ${result.length} scores");
return result;
}
}
「一個人一生可以愛上很多人。等你終於得到屬於你的幸福,你就會明白以前的傷痛其實是一種財富,它讓你學會把握和珍惜你愛的人。」 –電影【鐵達尼號】TITANIC
One may fall in love with many people during the lifetime. When you finally get your own happiness, you will understand the previous sadness is kind of treasure, which makes you better to hold and cherishthe people you love.-TITANIC
今天要聽林曉培的《心動》,金城武的帥臉怎能讓人不心動haha