오늘은 전체/할일/완료일을 구분할수 있는 필터 기능과 해당 필터에 맞는 리스트를 출력하는 기능을 구현해보자.
이전에는 필터 기능이 없어서 어떤 버튼(all/active/completed)을 선택해도 동일하게 Todo 리스트를 보여주었는데, 이번에는 필터에 따른 리스트 보여주기 기능을 추가할 예정이다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 2.10.3
소스코드 위치 - 04_filter_Filtered_Todos · mike-bskim/todo_test (github.com)
필터 추가 및 해당 필터별 리스트는 아래 동영상처럼 동작한다.
todo_filter.dart - 필터 상태를 저장하는 provider
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import '../models/todo_model.dart';
class TodoFilterState extends Equatable {
final Filter filter;
const TodoFilterState({
required this.filter,
});
factory TodoFilterState.init() {
return const TodoFilterState(filter: Filter.all);
}
@override
List<Object> get props => [filter];
@override
bool get stringify => true;
TodoFilterState copyWith({
Filter? filter,
}) {
return TodoFilterState(
filter: filter ?? this.filter,
);
}
}
class TodoFilter with ChangeNotifier {
TodoFilterState _state = TodoFilterState.init();
TodoFilterState get state => _state;
void changeFilter(Filter newFilter) {
_state = _state.copyWith(filter: newFilter);
notifyListeners();
}
}
filtered_todos.dart
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import '../models/todo_model.dart';
import 'providers.dart';
class FilteredTodosState extends Equatable {
final List<Todo> filteredTodos;
const FilteredTodosState({
required this.filteredTodos,
});
factory FilteredTodosState.initial() {
return const FilteredTodosState(filteredTodos: []);
}
@override
List<Object> get props => [filteredTodos];
@override
bool get stringify => true;
FilteredTodosState copyWith({
List<Todo>? filteredTodos,
}) {
return FilteredTodosState(
filteredTodos: filteredTodos ?? this.filteredTodos,
);
}
}
class FilteredTodos with ChangeNotifier {
FilteredTodosState _state = FilteredTodosState.initial();
FilteredTodosState get state => _state;
void update(
TodoFilter todoFilter,
TodoList todoList,
) {
List<Todo> _filteredTodos;
// 핵심 부분. 필터의 조건에 맞는 리스트를 만드는 기능
switch (todoFilter.state.filter) {
case Filter.active:
_filteredTodos =
todoList.state.todos.where((Todo todo) => !todo.completed).toList();
break;
case Filter.completed:
_filteredTodos =
todoList.state.todos.where((Todo todo) => todo.completed).toList();
break;
case Filter.all:
default:
_filteredTodos = todoList.state.todos;
break;
}
_state = _state.copyWith(filteredTodos: _filteredTodos);
notifyListeners();
}
}
providers.dart - provider 를 한곳에서 import 하게 모음
export 'todo_list.dart';
export 'todo_filter.dart';
export 'filtered_todos.dart';
main.dart - provider 기능 추가
return MultiProvider(
providers: [
ChangeNotifierProvider<TodoList>(create: (context) => TodoList()),
ChangeNotifierProvider<TodoFilter>(create: (context) => TodoFilter()),
ChangeNotifierProxyProvider2<TodoFilter, TodoList, FilteredTodos>(
create: (context) => FilteredTodos(),
update: (
BuildContext context,
TodoFilter todoFilter,
TodoList todoList,
FilteredTodos? filteredTodos,
) =>
// .. 은 cascade notation 입니다. 자세한건 구글링해보세요.
filteredTodos!..update(todoFilter, todoList),
),
],
child: MaterialApp(
title: 'TODOS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TodosScreen(),
),
);
cascade notation에 대해서 - https://dart.dev/guides/language/language-tour#cascade-notation
todo_model.dart 에 필터 enum 추가
enum Filter {
all,
active,
completed,
}
todos_screen.dart 수정 - 필터버튼 로직 수정, 선택된 필더 버튼 배경색 로직 수정,
// StatefulWidget => StatelessWidget 변경.
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: () {
context.read<TodoFilter>().changeFilter(filter);
debugPrint('Clicked button ${context.read<TodoFilter>().state.filter}');
},
child: Text(
filter == Filter.all
? 'All'
: filter == Filter.active
? 'Active'
: 'Completed',
style: TextStyle(
fontSize: 18.0,
color: textColor(context, filter),
),
),
);
}
Color textColor(BuildContext context, Filter filter) {
var currentFilter = context.watch<TodoFilter>().state.filter;
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) {
// TodoList => FilteredTodos 로 변경
// final todos = context.watch<TodoList>().state.todos;
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 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) {
debugPrint('todo list : ${widget.todo}');
return ListTile(
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()}');
// provider 처리해서 필요없음
// setState(() {});
},
),
title: Text(widget.todo.desc),
);
}
}
[참고자료] 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) - 2단계 모델링 및 리스트 관리 (0) | 2022.05.18 |
[Flutter] App Todo(with Provider) - 1단계 화면구성 (0) | 2022.05.13 |