상태관리에서 배운 provider를 이용하여 todo 어플을 만들어 보려고 합니다.
오늘은 화면을 먼저만들어 보겠습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 2.10.3
소스코드 위치 - 02_screen_layout · mike-bskim/todo_test (github.com)
전체 진행순서는 아래와 같다.
1. 화면 구성
2. 모델링 및 todo 리스트 관리(생성 및 완료체크)
3. 필터별 리스트 관리
4. 검색 기능추가(필터&검색, 필터와 검색 기능을 and 조건으로 구현)
5. 할일 개수 표시
6. todo 리스트 삭제 및 편집 기능
기본 화면 구성은 아래와 같다.
화면은 위와 같은 구성이다. 세부 위젯구성은 아래와 같다.
1. 어플 제목
2. Todo 생성
3. 검색 및 필터(전체, 미완료 리스트, 완료 리스트)
4. Todo 리스트
main.dart
import 'package:flutter/material.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) {
return MaterialApp(
title: 'TODOS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TodosScreen(),
);
}
}
todos_screen.dart
import 'package:flutter/material.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(),
],
),
),
),
),
);
}
}
// AppBar 사용하지 않고 제목 표시함.
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,
),
),
],
);
}
}
// 새로운 todo 리스트 생성
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) {
return TextButton(
onPressed: () {
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;
}
}
// todo 클래스, 모델을 만들어서 별도의 파일로 분리예정.
class Todo {
String id;
String desc;
bool completed;
Todo({
required this.id,
required this.desc,
this.completed = false,
});
}
// 임시 화면표시용 리스트
List<Todo> todos = [
Todo(id: '1', desc: 'Clean the room'),
Todo(id: '2', desc: 'Wash the dish'),
Todo(id: '3', desc: 'Do homework'),
];
// todo 리스트 보여주는 부분.
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 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 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> {
@override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: widget.todo.completed,
onChanged: (bool? checked) {
widget.todo.completed = !widget.todo.completed;
debugPrint('value(${widget.todo.desc}): ${widget.todo.completed.toString()}');
setState(() {
});
},
),
title: Text(widget.todo.desc),
);
}
}
차후에 Provider 로 업데이트에 따라 각 위젯의 타입을 "StatelessWidget" 으로 변경예정임.
[참고자료] udemy - Flutter Provider Essential 코스 (Korean)
'Flutter > 10 app Todo with provider' 카테고리의 다른 글
[Flutter] App Todo(with Provider) - 6단계 리스트 삭제, 편집 (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 |
[Flutter] App Todo(with Provider) - 2단계 모델링 및 리스트 관리 (0) | 2022.05.18 |