依照官網的教學,是直接在main()
裡面進行所有操作,但是為了看起來更好閱讀更簡潔,建議新增一個單獨的資料夾database/
,裡面存放要建立的${dbName}.dart
檔案。
用TodoList當作範例:
資料夾結構如下:
lib/
- database/
⊢ TodoDB.dart
- pages/
⊢ TodoList.dart
- app.dart
- main.dart
要建立一個db,大概會經過以下步驟:
- 安裝package
- 創建/連接資料庫
- 創建table
- 實作CRUD
- 在頁面中引入,呼叫Function
安裝package
安裝sqflite
跟path
兩個package,在pubspec.yaml
中加入:
兩個套件的最新版本可以上網查
dependencies:
sqflite: ^1.3.0
path: ^1.6.4
加完之後重跑一次:
$ flutter pub get
創建/連接資料庫
因為不想直接放在main()
裡,所以要自己創建一個class
,可以放在獨立的資料夾。
在lib/database/TodoDB.dart
中新增以下程式碼:
引入所有會用到的package
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
創建一個schema
class Todo {
final String id;
final String name;
final int isCompleted; Todo({this.id, this.name, this.isCompleted});
}
創建一個實例
class TodoDB {
// 裡面會放所有db的動作
}
連接資料庫
其中的todo.db
是幫你的db取一個檔名,之後他會用這個檔名存成檔案在本地的某個資料夾中。
class TodoDB {
static Database database;
static Future<Database> initDatabase() async {
database = await openDatabase(
join(await getDatabasesPath(), 'todo.db'),
);
return database;
}
}
為了不讓資料庫每次都要重新建立,所以加上一個防呆的判斷
class TodoDB { //...initDatabase
static Future<Database> getDBConnect() async {
if (database != null) {
return database;
}
return await initDatabase();
}
}
創建table
openDatabase
的第二個參數是創建table。execute
裡面放的是SQLite語法,todos
是我自己取的table名稱。
static Future<Database> initDatabase() async {
database = await openDatabase(
join(await getDatabasesPath(), 'todo.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE todos(id TEXT PRIMARY KEY, name TEXT, isCompleted INTEGER)",
);
},
version: 1,
);
return database;
}
到目前為止的TodoDB.dart
完整程式碼:
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';class Todo {
final String id;
final String name;
final int isCompleted; Todo({this.id, this.name, this.isCompleted});
}class TodoDB {
static Database database; static Future<Database> getDBConnect() async {
if (database != null) {
return database;
}
return await initDatabase();
} static Future<Database> initDatabase() async {
database = await openDatabase(
join(await getDatabasesPath(), 'todo.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE todos(id TEXT PRIMARY KEY, name TEXT, isCompleted INTEGER)",
);
},
version: 1,
);
return database;
}
}
實作CRUD
拿取資料
- 為了讓schema可以產出直接可以使用的資料,所以先修改
Todo
讓它return出Map
的格式
class Todo {
final String id;
final String name;
final int isCompleted; Todo({this.id, this.name, this.isCompleted});
// 加上下面這些...
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'isCompleted': isCompleted,
};
}
}
- 實作get方法:
query
裡面放的是table名稱。
class TodoDB {
//...initDatabase
static Future<List<Todo>> getTodos() async {
final Database db = await getDBConnect();
final List<Map<String, dynamic>> maps = await db.query('todos');
return List.generate(maps.length, (i) {
return Todo(
id: maps[i]['id'],
name: maps[i]['name'],
isCompleted: maps[i]['isCompleted'],
);
});
}
}
新增一筆
傳進來的參數,被宣告成Todo
的型態,參數名稱叫做todo
,到時候頁面中呼叫這個function的時候要帶參數進來。
class TodoDB {
//...initDatabase
static Future<void> addTodo(Todo todo) async {
final Database db = await getDBConnect();
await db.insert(
'todos',
todo.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
編輯
在設計的時候id
規定是唯一不重複的值,所以可以用id
去找到唯一一筆資料修改。
class TodoDB {
//...initDatabase
static Future<void> updateTodo(Todo todo) async {
final Database db = await getDBConnect();
await db.update(
'todos',
todo.toMap(),
where: "id = ?",
whereArgs: [todo.id],
);
}
}
刪除
因為刪除只需要id
去找到該筆資料然後刪掉,所以呼叫的時候,只需要傳id
進來,不需要給整個Todo
。
class TodoDB{
static Future<void> deleteTodo(String id) async {
final Database db = await getDBConnect();
await db.delete(
'todos',
where: "id = ?",
whereArgs: [id],
);
}
}
在頁面中引入,呼叫Function
lib/pages/TodoList.dart
:
引入剛剛創建的TodoDB.dart
import '../database/TodoDB.dart';
查詢
- 在頁面中宣告:
因為我們有引入檔案,所以可以直接在widget
中呼叫TodoDB.getTodos()
class _ListState extends State<ListWidget> {
List<Todo> listArr = []; // 查所有list
void getTodoList() async {
final list = await TodoDB.getTodos();
setState(() {
listArr = list;
});
}
}
- 顯示:
//...children: [
for (int index = 1; index <= listArr.length; index++)
ListTile(
leading: Checkbox(
value: listArr[index - 1].isCompleted == 1),
title: Text('${listArr[index - 1].name}'),
)
]
新增
- 在頁面中宣告:
void addTodo() async {
final newTodo = Todo(
id: new DateTime.now().millisecondsSinceEpoch.toString(),
name: 'new Todo',
isCompleted: 0,
);
await TodoDB.addTodo(newTodo);
getTodoList();
}
- 按鈕呼叫
FloatingActionButton(
onPressed: addTodo, child: const Icon(Icons.add))
修改(打勾完成,只修改isCompleted
)
- 在頁面中宣告:
void onChangeCheckbox(val, todo) async {
final updateTodo =
Todo(id: todo.id, name: todo.name, isCompleted: val ? 1 : 0);
await TodoDB.updateTodo(updateTodo);
getTodoList();
}
- 畫面呼叫
for (int index = 1; index <= listArr.length; index++)
ListTile(
leading: Checkbox(
value: listArr[index - 1].isCompleted == 1,
onChanged: (val) {
onChangeCheckbox(val, listArr[index - 1]);
}))
整頁完整程式碼
import 'package:flutter/material.dart';
import '../database/TodoDB.dart';enum extraAction { edit, delete }class ListWidget extends StatefulWidget {
@override
_ListState createState() => _ListState();
}class _ListState extends State<ListWidget> {
List<Todo> listArr = []; // 查所有list
void getTodoList() async {
final list = await TodoDB.getTodos();
setState(() {
listArr = list;
});
} // 打勾
void onChangeCheckbox(val, todo) async {
final updateTodo =
Todo(id: todo.id, name: todo.name, isCompleted: val ? 1 : 0);
await TodoDB.updateTodo(updateTodo);
getTodoList();
} // 新增
void addTodo() async {
final newTodo = Todo(
id: new DateTime.now().millisecondsSinceEpoch.toString(),
name: 'new Todo',
isCompleted: 0,
);
await TodoDB.addTodo(newTodo);
getTodoList();
} // 選擇編輯 or 刪除
void editList(type, context, todo) {
switch (type) {
case extraAction.edit:
Navigator.push<void>(
context,
MaterialPageRoute(
builder: (context) => FullScreenDialog(
onSave: editTodo, todo: todo, onDelete: deleteTodo),
fullscreenDialog: true));
break;
case extraAction.delete:
deleteTodo(todo);
break;
default:
print('error!!');
}
} // 編輯
void editTodo(name, todo) async {
final updateTodo =
Todo(id: todo.id, name: name, isCompleted: todo.isCompleted);
await TodoDB.updateTodo(updateTodo);
getTodoList();
Navigator.pop(context);
} // 刪除
void deleteTodo(todo) async {
await TodoDB.deleteTodo(todo.id);
getTodoList();
} @override
void initState() {
super.initState();
getTodoList();
} void dispose() {
super.dispose();
} Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TodoList'),
),
body: Column(
children: <Widget>[
Expanded(
child: ListView(
children: [
for (int index = 1; index <= listArr.length; index++)
ListTile(
leading: Checkbox(
value: listArr[index - 1].isCompleted == 1,
onChanged: (val) {
onChangeCheckbox(val, listArr[index - 1]);
}),
title: Text('${listArr[index - 1].name}',
style: TextStyle(
color: listArr[index - 1].isCompleted == 1
? Colors.grey.shade400
: Theme.of(context).textTheme.bodyText1.color,
decoration: listArr[index - 1].isCompleted == 1
? TextDecoration.lineThrough
: null)),
trailing: PopupMenuButton<extraAction>(
onSelected: (type) {
editList(type, context, listArr[index - 1]);
},
itemBuilder: (BuildContext context) =>
<PopupMenuItem<extraAction>>[
PopupMenuItem<extraAction>(
value: extraAction.edit,
child: Text('Edit'),
),
PopupMenuItem<extraAction>(
value: extraAction.delete,
child: Text('Delete'),
),
],
),
)
],
),
),
MaterialBanner(
content: Text(
'共 ${listArr.length} 個清單,已完成 ${listArr.where((obj) => obj.isCompleted == 1).length} 個'),
actions: <Widget>[null],
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: addTodo, child: const Icon(Icons.add)),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
);
}
}class FullScreenDialog extends StatefulWidget {
FullScreenDialog({this.onSave, this.todo, this.onDelete});
final Function onSave;
final Todo todo;
final Function onDelete;
@override
_FullScreenDialogState createState() => _FullScreenDialogState();
}class _FullScreenDialogState extends State<FullScreenDialog> {
final itemController = TextEditingController(); @override
void initState() {
super.initState();
itemController.text = widget.todo.name;
} void dispose() {
itemController.dispose();
super.dispose();
} Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('add a todo'),
actions: <Widget>[
FlatButton(
onPressed: () {
widget.onSave(itemController.text, widget.todo);
},
child: Text('儲存'))
],
),
body: Center(
child: Container(
padding: const EdgeInsets.all(30.0),
child: TextField(
controller: itemController,
obscureText: false,
decoration:
InputDecoration(labelText: 'label', hintText: '請輸入項目名稱'),
),
),
),
);
}
}class TodoList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListWidget();
}
}