상태관리에서 배운 Getx를 이용하여 todo 어플을 만들어 보려고 합니다.
이전 버전은 provider 로 상태관리하는 todo 어플을 만들었다.
오늘은 화면, 모델링 및 리스트 관리 기능을 먼저만들어 보겠습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 2.10.3
소스코드 위치 - Release 02_model&todolist · mike-bskim/todo_getx · GitHub
이전 provider 로 구현한 블로그는 아래와 같다.
2022.05.13 - [Flutter/10 app Todo with provider] - [Flutter] App Todo(with Provider) - 1단계 화면구성
화면구성은 아래와 같다.
오늘의 프로젝트 주요파일은 아래와 같다.
./main.dart
./bindings/todo_binding.dart
./controller/todo_list_controller.dart
./model/todo_model.dart
./screens/todos_screen.dart
./main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'bindings/todo_binding.dart';
import 'screens/todos_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// GetMaterialApp 으로 변경, from MaterialApp
return GetMaterialApp(
// 화면이 하나라서 초기 바인딩으로 dependency injection 함
initialBinding: TodoBinding(),
title: 'TODOS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TodosScreen(),
);
}
}
./bindings/todo_binding.dart
import 'package:get/get.dart';
import '../controller/todo_list_controller.dart';
class TodoBinding extends Bindings {
@override
void dependencies() {
// dependency injection
Get.put<TodoList>(TodoList());
}
}
./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'),
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));
}
}
./model/todo_model.dart
import 'package:uuid/uuid.dart';
Uuid uuid = const Uuid();
class Todo {
final String id;
final String desc;
final bool completed;
// id를 입력하지 않으면 기본값으로 설정됨
Todo({
String? id,
required this.desc,
this.completed = false,
}) : id = id ?? uuid.v4();
@override
String toString() {
return 'Todo(id: $id, desc: $desc, completed: $completed)';
}
}
./screens/todos_screen.dart - 전체 리스트만 제외하고 더미값으로 화면에 표시함.
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 StatefulWidget {
const TodosScreen({Key? key}) : super(key: key);
@override
State<TodosScreen> createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
@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 {
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}');
},
);
}
}
class SearchAndFilterTodo extends StatefulWidget {
const SearchAndFilterTodo({Key? key}) : super(key: key);
@override
State<SearchAndFilterTodo> createState() => _SearchAndFilterTodoState();
}
class _SearchAndFilterTodoState extends State<SearchAndFilterTodo> {
String clickedType = 'all';
@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, 'all'),
filterButton(context, 'active'),
filterButton(context, 'completed'),
],
),
],
);
}
Widget filterButton(BuildContext context, String filter) {
//, Filter filter
return TextButton(
onPressed: () {
// context.read<TodoFilter>().changeFilter(filter);
clickedType = filter;
debugPrint('Clicked button $clickedType');
setState(() {});
},
child: Text(
filter == 'all'
? 'All'
: filter == 'active'
? 'Active'
: 'Completed',
style: TextStyle(
fontSize: 18.0,
color: textColor(context, filter),
),
),
);
}
Color textColor(BuildContext context, String filter) {
var currentFilter = clickedType;
return currentFilter == filter ? Colors.blue : Colors.grey;
}
}
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) {
return Obx(() {
final currentTodos = TodoList.to.todos;
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 StatefulWidget {
final Todo todo;
const TodoItem({Key? key, required this.todo}) : super(key: key);
@override
_TodoItemState createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> {
@override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: widget.todo.completed,
onChanged: (bool? checked) {
debugPrint('clicked toggle button~~');
},
),
title: Text(widget.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) - 2단계 추가, 필터링 및 리스트 (0) | 2022.06.14 |