owned this note
owned this note
Published
Linked with GitHub
# 不專業教學系列 - Flutter To-Do List 第二集(番外篇) 典型的亡羊補牢
>tag: `Flutter`、`Dart`
突然發現新增事項的時候,沒有輸入內容也可以新增資料

這很明顯是有問題的,為了解決這問題,我們可以檢查文字輸入框的`Controller.Text` 是否為空字串。
## 新增驗證器
宣告一個Key叫做`_todoFormKey`。
```dart
final GlobalKey<FormState> _todoFormKey = GlobalKey<FormState>();
```
`GlobalKey<FormState>` 是用來唯一標識一個 Form 小部件的全局鍵。它允許我們在應用程式的其他部分訪問和操作這個表單的狀態。
接下來搞定文字輸入框,原本的文字輸入框是使用兩個`TextFeild`,現在我們改用`TextFormFeild`,修改`floatingActionButton`的`onPressed()`事件,將`AlertDialog`的`content`改寫:
```dart
// display_todo_screen.dart
content: Form(
key: _todoFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
controller: _todoTitleController,
decoration: const InputDecoration(hintText: "待辦事項名稱"),
validator: (value) {
if (value == null || value.isEmpty) {
return '名稱不能為空!';
}
// 如果內容只有一堆空格,一樣回傳錯誤
// value.trim() 是將字串中的空格都去掉
// 這樣我們就可以檢查處理過的字串有沒有問題
if (value.trim().isEmpty){
return '請輸入名稱!';
}
return null;
},
),
TextFormField(
controller: _todoContentController,
decoration: const InputDecoration(hintText: "待辦事項內容"),
validator: (value) {
if (value == null || value.isEmpty) {
return '內容不能為空!';
}
if (value.trim().isEmpty){
return '請輸入內容!';
}
return null;
},
),
],
),
),
```
可以看到我們增加了一個酷東西`validator`,它是對我們的TextFormField做驗證用的,可以在裡面對文字做一些條件限制。
為了要檢查文字是否符合規定,我們要修改`新增`按鈕的`onPressed()`事件:
```dart
// display_todo_screen.dart
// 調用_todoFormKey
if (_todoFormKey.currentState!.validate()) {
final provider =
Provider.of<TodoProvider>(context, listen: false);
provider.addTodo(TodoData(
title: _todoTitleController.text,
content: _todoContentController.text,
isChecked: false,
));
_todoTitleController.clear();
_todoContentController.clear();
Navigator.pop(context);
}
```
## 成果
來看看成果吧!

### 完整程式碼
今天只有改動`display_todo_screen.dart`,所以直接貼上完整程式碼:
```dart
import 'package:flutter/material.dart';
import 'package:todo_app/models/todo_data.dart';
import 'package:todo_app/providers/todo_provider.dart';
import 'package:provider/provider.dart';
class DisplayTodoScreen extends StatefulWidget {
const DisplayTodoScreen({super.key});
@override
State<DisplayTodoScreen> createState() {
return _DisplayTodoListState();
}
}
class _DisplayTodoListState extends State<DisplayTodoScreen> {
final TextEditingController _todoTitleController = TextEditingController();
final TextEditingController _todoContentController = TextEditingController();
final GlobalKey<FormState> _todoFormKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: _showTodos(),
),
_complectedButton(),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.blueGrey.shade600,
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("新增待辦事項",
style: TextStyle(fontWeight: FontWeight.bold)),
content: Form(
key: _todoFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
controller: _todoTitleController,
decoration: const InputDecoration(hintText: "待辦事項名稱"),
validator: (value) {
if (value == null || value.isEmpty) {
return '名稱不能為空!';
}
if (value.trim().isEmpty){
return '請輸入名稱!';
}
return null;
},
),
TextFormField(
controller: _todoContentController,
decoration: const InputDecoration(hintText: "待辦事項內容"),
validator: (value) {
if (value == null || value.isEmpty) {
return '內容不能為空!';
}
if (value.trim().isEmpty){
return '請輸入內容!';
}
return null;
},
),
],
),
),
actions: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueGrey.shade600,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
onPressed: () {
Navigator.pop(context);
_todoTitleController.clear();
_todoContentController.clear();
},
child: const Text(
"取消",
style: TextStyle(color: Colors.white),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueGrey.shade600,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
onPressed: () {
// 先檢查文字
if (_todoFormKey.currentState!.validate()) {
final provider =
Provider.of<TodoProvider>(context, listen: false);
provider.addTodo(TodoData(
title: _todoTitleController.text,
content: _todoContentController.text,
isChecked: false,
));
_todoTitleController.clear();
_todoContentController.clear();
Navigator.pop(context);
}
},
child: const Text(
"新增",
style: TextStyle(color: Colors.white),
),
),
],
);
},
);
},
child: const Icon(Icons.playlist_add_rounded, color: Colors.white),
),
);
}
Widget _showTodos() {
// 取得 Provider 中的資料
final provider = Provider.of<TodoProvider>(context);
return ListView.builder(
itemCount: provider.todoList.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
ListTile(
// 改用 Provider 來顯示待辦事項
title: Text(provider.todoList[index].title,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 20)),
subtitle: Text(provider.todoList[index].content,
style: const TextStyle(fontSize: 16)),
leading: Checkbox(
activeColor: Colors.blueGrey.shade600,
checkColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
value: provider.todoList[index].isChecked,
onChanged: (bool? value) {
// 呼叫 Provider 來更新待辦事項的狀態
provider.updateTodoCheck(index, value!);
},
),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.blueGrey),
onPressed: () {
provider.removeTodoAt(index);
},
),
),
const Divider(),
],
);
},
);
}
Widget _complectedButton() {
return Padding(
padding: const EdgeInsets.only(top: 10, bottom: 22),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueGrey.shade600,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
minimumSize: const Size(130, 50),
maximumSize: const Size(130, 50),
),
onPressed: () {
// 呼叫 Provider 來清除已完成的待辦事項
Provider.of<TodoProvider>(context, listen: false).clearCompleted();
},
child: const Text(
"清除完成事項",
style: TextStyle(color: Colors.white, fontSize: 12),
)),
);
}
}
```
## 結語
實在是沒想到忘記去檢查文字輸入時的狀況,導致有奇怪的東西混進todo了XD
如果沒處理好,到了後面要吧資料存進資料庫時就有可能GG了哈哈,趕快去修復你的To-Do List吧!
感謝觀看!