상태관리에서 배운 Getx를 이용하여 todo 어플을 만들어 보려고 합니다.
이전 버전은 provider 로 상태관리하는 todo 어플을 만들었다.
오늘은 새로운 todo 추가, 필터(all/active/completed) 에 따른 todo 리스트 보여주기 기능을 만들어 보겠습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 2.10.3
소스코드 위치 - Release 03_filter&filteredList2 · mike-bskim/todo_getx · GitHub
화면구현은 아래와 같습니다. 새로 추가된 리스트("추가된 리스트")가 리스트 하단에 있습니다.
오늘의 프로젝트 주요파일은 아래와 같다.
./bindings/todo_binding.dart
./controller/todo_list_controller.dart
./model/todo_model.dart
./screens/todos_screen.dart
./bindings/todo_binding.dart - controller 2개 추가
import 'package:get/get.dart';
import '../controller/todo_list_controller.dart';
class TodoBinding extends Bindings {
@override
void dependencies() {
// dependency injection
Get.put<TodoList>(TodoList());
Get.put<TodosFilter>(TodosFilter());
Get.put<FilteredTodos>(FilteredTodos());
}
}
./controller/todo_list_controller.dart
import 'package:get/get.dart';
import '../model/todo_model.dart';
class TodoList extends GetxController {
// 샘플 데이터 생성
RxList<Todo> todos = <Todo>[
Todo(id: '1', desc: 'Clean the room', completed: true),
Todo(id: '2', desc: 'Do homework'),
Todo(id: '3', desc: 'Wash the dish'),
].obs;
static TodoList get to => Get.find();
void addTodo({required String todoDesc}) {
int? newNum;
if (todos.isEmpty) {
newNum = 1;
} else {
// 마지막 id 에서 1 증가
newNum = int.parse(todos.last.id) + 1;
}
todos.add(Todo(id: newNum.toString(), desc: todoDesc));
}
// 토글 함수 추가.
void toggleTodo(String id) {
todos.assignAll(todos.map((todo) {
return todo.id == id
? Todo(
id: id,
desc: todo.desc,
completed: !todo.completed,
)
: todo;
}).toList());
}
}
class TodosFilter extends GetxController {
Rx<Filter> todosFilter = Filter.all.obs;
static TodosFilter get to => Get.find();
}
class FilteredTodos extends GetxController {
// 감시해야할 값 추가
final todos = TodoList.to.todos;
final filter = TodosFilter.to.todosFilter;
RxList<Todo> filteredTodos = <Todo>[].obs;
static FilteredTodos get to => Get.find();
@override
void onInit() {
// 최초 화면에 표시할 정보 추가
filteredTodos.assignAll(todos);
// 감시해야할 값들은 리스트 형태로 전달, 값들중 하나라도 변경되면 everAll 자동으로 호출됨
// 자동으로 리스트정보가 갱신되고 화면에 다시 rendering 됨
everAll([todos, filter], (_) {
List<Todo> tempTodos;
switch (filter.value) {
case Filter.active:
tempTodos = todos.where((todo) => !todo.completed).toList();
break;
case Filter.completed:
tempTodos = todos.where((todo) => todo.completed).toList();
break;
case Filter.all:
default:
tempTodos = todos.toList();
break;
}
filteredTodos.assignAll(tempTodos);
});
super.onInit();
}
}
./model/todo_model.dart - enum 추가
enum Filter {
all,
active,
completed,
}
./screens/todos_screen.dart - todo 기능 추가, 필터 기능 추가, 필터링된 리스트 기능 추가
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:todo_test/controller/todo_list_controller.dart';
import '../model/todo_model.dart';
class TodosScreen extends StatelessWidget {
const TodosScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40.0),
child: Column(
children: const [
TodoHeader(),
CreateTodo(),
SizedBox(height: 20.0),
SearchAndFilterTodo(),
ShowTodos(),
],
),
),
),
),
);
}
}
class TodoHeader extends StatelessWidget {
const TodoHeader({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'TODO',
style: TextStyle(fontSize: 40.0),
),
Text(
'0 items left',
style: TextStyle(
fontSize: 20.0,
color: Colors.redAccent,
),
),
],
);
}
}
class CreateTodo extends StatefulWidget { //StatefulWidget//StatelessWidget
const CreateTodo({Key? key}) : super(key: key);
@override
_CreateTodoState createState() => _CreateTodoState();
}
class _CreateTodoState extends State<CreateTodo> {
final newTodoController = TextEditingController();
@override
void dispose() {
newTodoController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextFormField(
controller: newTodoController,
decoration: const InputDecoration(labelText: 'What to do?'),
onFieldSubmitted: (String? todoDesc) {
debugPrint('CreateTodo Clicked: ${newTodoController.text}');
if (todoDesc != null && todoDesc.trim().isNotEmpty) {
TodoList.to.addTodo(todoDesc: todoDesc);
newTodoController.clear();
}
},
);
}
}
class SearchAndFilterTodo extends StatelessWidget {
const SearchAndFilterTodo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Search todos',
border: InputBorder.none,
filled: true,
prefixIcon: Icon(Icons.search),
),
onChanged: (String? newSearchTerm) {
debugPrint('Search todos: $newSearchTerm');
},
),
const SizedBox(height: 10.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
filterButton(context, Filter.all),
filterButton(context, Filter.active),
filterButton(context, Filter.completed),
],
),
],
);
}
Widget filterButton(BuildContext context, Filter filter) {
return TextButton(
onPressed: () {
// 일반적으로는 함수처리해야 함. 직접 접근하지 말것
TodosFilter.to.todosFilter.value = filter;
debugPrint('Clicked button $filter');
},
child: Obx(
() => Text(
filter == Filter.all
? 'All'
: filter == Filter.active
? 'Active'
: 'Completed',
style: TextStyle(
fontSize: 18.0,
color: textColor(context, filter),
fontWeight: textFontWeight(context, filter),
),
),
),
);
}
Color textColor(BuildContext context, Filter filter) {
final currentFilter = TodosFilter.to.todosFilter;
return currentFilter.value == filter ? Colors.blue : Colors.grey;
}
FontWeight textFontWeight(BuildContext context, Filter filter) {
var currentFilter = TodosFilter.to.todosFilter;
return currentFilter.value == filter ? FontWeight.bold : FontWeight.normal;
}
}
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 currentTodos = FilteredTodos.to.filteredTodos;
return Obx(() {
return ListView.separated(
primary: false,
shrinkWrap: true,
itemCount: currentTodos.length,
separatorBuilder: (BuildContext context, int index) {
return const Divider(color: Colors.grey);
},
itemBuilder: (BuildContext context, int index) {
return TodoItem(todo: currentTodos[index]);
},
);
});
}
}
class TodoItem extends StatelessWidget {
final Todo todo;
const TodoItem({Key? key, required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: todo.completed,
onChanged: (bool? checked) {
debugPrint('clicked toggle button~~');
TodoList.to.toggleTodo(todo.id);
},
),
title: Text(todo.desc),
);
}
}
[참고자료] 헤비프랜
- https://www.youtube.com/watch?v=HZJsKlN-kmc&list=PLGJ958IePUyDQwYbPcz-5W9o4p1__20V0&index=5
'Flutter > 10 app Todo with GetX' 카테고리의 다른 글
[Flutter] App Todo(with Getx) - 5단계 todo 편집 refactoring (0) | 2022.06.16 |
---|---|
[Flutter] App Todo(with Getx) - 4단계 todo 편집, 삭제 (0) | 2022.06.16 |
[Flutter] App Todo(with Getx) - 3단계 검색, 할일 개수 (0) | 2022.06.15 |
[Flutter] App Todo(with Getx) - 1단계 화면, 모델링 및 리스트 관리 (0) | 2022.06.14 |