오늘은 todo 리스트 내용을 편집하거나 리스트 자체를 삭제하는 기능을 구현하겠습니다.
그리고 active 리스트 개수와 FilteredTodos 의 생성자도 약간 수정했습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 2.10.3
소스코드 위치 - 07_remove_edit_todoList · mike-bskim/todo_test (github.com)
todo_list.dart 수정 - 편집, 삭제 기능 추가
// 수정, id 값이 같으면 desc 를 변경.
void editTodo(String id, String desc) {
final newTodos = _state.todos.map((Todo todo) {
if (todo.id == id) {
return Todo(
id: id,
desc: desc,
completed: todo.completed,
);
}
return todo;
}).toList();
_state = _state.copyWith(todos: newTodos);
notifyListeners();
}
// 삭제, id 값이 다른 리스트만 반환.
void removeTodo(String id) {
final newTodos = _state.todos.where((Todo todo) => todo.id != id).toList();
_state = _state.copyWith(todos: newTodos);
debugPrint('remove Todos: ' + _state.toString());
notifyListeners();
}
todos_screen.dart 수정 -
class ShowTodos extends StatelessWidget {
const ShowTodos({Key? key}) : super(key: key);
// 삭제시, 아이콘 및 붉은 배경색 표시
Widget showBackground(int direction) {
return Container(
margin: const EdgeInsets.all(4.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
color: Colors.red,
alignment: direction == 0 ? Alignment.centerLeft : Alignment.centerRight,
child: const Icon(
Icons.delete,
size: 30.0,
color: Colors.white,
),
);
}
@override
Widget build(BuildContext context) {
final todos = context.watch<FilteredTodos>().state.filteredTodos;
return ListView.separated(
primary: false,
shrinkWrap: true,
itemCount: todos.length,
separatorBuilder: (BuildContext context, int index) {
return const Divider(color: Colors.grey);
},
itemBuilder: (BuildContext context, int index) {
// 삭제용 위젯
return Dismissible(
key: ValueKey(todos[index].id),
background: showBackground(0),
secondaryBackground: showBackground(1),
onDismissed: (_) {
// 해당 리스트 삭제, 리스트의 id 를 전달
context.read<TodoList>().removeTodo(todos[index].id);
},
confirmDismiss: (_) {
// 삭제시 확인용 팝업창 표시
return showDialog(
context: context,
barrierDismissible: false, // 다이얼로그 외부 클릭시 없어지지 않음
builder: (context) {
return AlertDialog(
title: const Text('Are you sure?'),
content: const Text('Do you really want to delete?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('NO'),
),
TextButton(
// if true, onDismissed 실행
onPressed: () => Navigator.pop(context, true),
child: const Text('YES'),
),
],
);
},
);
},
child: TodoItem(todo: todos[index]),
);
},
);
}
}
class TodoItem extends StatefulWidget {
final Todo todo;
const TodoItem({Key? key, required this.todo}) : super(key: key);
@override
_TodoItemState createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> {
// 새로운 desc 를 저장할 controller
late final TextEditingController textController;
@override
void initState() {
super.initState();
textController = TextEditingController();
}
@override
void dispose() {
textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
bool _error = false;
textController.text = widget.todo.desc;
return StatefulBuilder( // errorText 를 다시 그려야 해서 사용함.
builder: (BuildContext context, StateSetter setState) {
return AlertDialog(
title: const Text('Edit Todo'),
content: TextFormField(
controller: textController,
autofocus: true,
decoration: InputDecoration(
errorText: _error ? 'Value cannot be empty' : null,
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('CANCEL'),
),
TextButton(
onPressed: () {
setState(() {
_error = textController.text.isEmpty ? true : false;
if (!_error) { // if empty, skip if.
context.read<TodoList>().editTodo(
widget.todo.id,
textController.text,
);
Navigator.pop(context);
}
});
},
child: const Text('EDIT'),
),
],
);
},
);
},
);
},
leading: Checkbox(
value: widget.todo.completed,
onChanged: (bool? checked) {
context.read<TodoList>().toggleTodo(widget.todo.id);
debugPrint(
'value(${widget.todo.desc}): ${widget.todo.completed
.toString()}');
},
),
title: Text(widget.todo.desc),
);
}
}
main.dart 수정
providers: [
ChangeNotifierProvider<TodoList>(create: (context) => TodoList()),
ChangeNotifierProvider<TodoFilter>(create: (context) => TodoFilter()),
ChangeNotifierProvider<TodoSearch>(create: (context) => TodoSearch()),
ChangeNotifierProxyProvider3<TodoFilter, TodoSearch, TodoList,
FilteredTodos>(
// 생성시 TodoList 를 확인해서 초기화함.
create: (context) => FilteredTodos(
initFilteredTodos: context.read<TodoList>().state.todos,
),
// 생성시 TodoList 를 확인해서 초기화하지 않아도 update 시 처리됨.
update: (
BuildContext context,
TodoFilter todoFilter,
TodoSearch todoSearch,
TodoList todoList,
FilteredTodos? filteredTodos,
) =>
filteredTodos!..update(todoFilter, todoSearch, todoList),
),
ChangeNotifierProxyProvider<TodoList, TodoActiveCount>(
// 생성시 TodoList 를 확인해서 초기화함.
create: (context) => TodoActiveCount(
initTodoActiveCount: context.read<TodoList>().state.todos.length,
),
// 생성시 TodoList 를 확인해서 초기화하지 않아도 update 시 처리됨.
update: (
BuildContext context,
TodoList todoList,
TodoActiveCount? todoActiveCount,
) =>
todoActiveCount!..update(todoList),
)
],
todo_active_count.dart 수정 - 도입부만 수정
class TodoActiveCount with ChangeNotifier {
// TodoActiveCountState _state = TodoActiveCountState.init();
late TodoActiveCountState _state;
final int initTodoActiveCount;
TodoActiveCount({
required this.initTodoActiveCount,
}) {
debugPrint('initTodoActiveCount: '+ initTodoActiveCount.toString());
_state = TodoActiveCountState(todoActiveCount: initTodoActiveCount);
}
TodoActiveCountState get state => _state;
filtered_todos.dart 수정 - 도입부만 수정
class FilteredTodos with ChangeNotifier {
// FilteredTodosState _state = FilteredTodosState.initial();
late FilteredTodosState _state;
final List<Todo> initFilteredTodos;
FilteredTodos({required this.initFilteredTodos}){
debugPrint('initFilteredTodos: ' + initFilteredTodos.toString());
_state = FilteredTodosState(filteredTodos: initFilteredTodos);
}
FilteredTodosState get state => _state;
그외 검색시 시간 딜레이를 추가하여 검색 부담을 줄임.
debounce.dart 생성
import 'dart:async';
import 'package:flutter/material.dart';
class Debounce {
final int milliseconds;
Debounce({
this.milliseconds = 500,
});
Timer? _timer;
void run(VoidCallback action) {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
[참고자료] udemy - Flutter Provider Essential 코스 (Korean)
'Flutter > 10 app Todo with provider' 카테고리의 다른 글
[Flutter] App Todo(refactoring StateNotifierProvider) (0) | 2022.05.25 |
---|---|
[Flutter] App Todo(ChangeNotifierProxyProvider -> ProxyProvider) (0) | 2022.05.20 |
[Flutter] App Todo(with Provider) - 5단계 할일 개수 표시 (0) | 2022.05.20 |
[Flutter] App Todo(with Provider) - 4단계 검색 (0) | 2022.05.20 |
[Flutter] App Todo(with Provider) - 3단계 필터 및 리스트, cascade notation (0) | 2022.05.18 |