본문 바로가기

Flutter/10 app Todo with provider

[Flutter] App Todo(with Provider) - 2단계 모델링 및 리스트 관리

Todo 리스트를 관리하는 provider 를 만들어 보자.

우선 모델을 만들고, 리스트 관리는 provider 를 이용하여 구현하였다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 2.10.3

소스코드 위치 - Release 03_model&todo_list · mike-bskim/todo_test (github.com)

 

Release 03_model&todo_list · mike-bskim/todo_test

 

github.com

 

todo_model.dart - 모델링

 

import 'package:equatable/equatable.dart';
import 'package:uuid/uuid.dart';

// unique 번호 생성
Uuid uuid = const Uuid();

class Todo extends Equatable{
  final String id;
  final String desc;
  final bool completed;

  // id 값을 넣지않으면 id값을 자동 초기화 함.
  Todo({
    String? id,
    required this.desc,
    this.completed = false,
  }) : id = id ?? uuid.v4();

  @override
  // TODO: implement props
  List<Object?> get props => [id, desc, completed];

  @override
  String toString() {
    return 'Todo{id: $id, completed: $completed, desc: $desc}';
  }
}

 

todo_list.dart - 리스트 관리

 

import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';

import '../models/todo_model.dart';

class TodoListState extends Equatable {
  final List<Todo> todos;
  const TodoListState({
    required this.todos,
  });

  factory TodoListState.init() {
    return TodoListState(todos: [
      Todo(id: '1', desc: 'Clean the room'),
      Todo(id: '2', desc: 'Wash the dish'),
      Todo(id: '3', desc: 'Do homework'),
    ]);
  }

  @override
  List<Object> get props => [todos];

  // @override
  // bool get stringify => true;

  @override
  String toString() {
    return 'TodoListState{todos: $todos}';
  }

  TodoListState copyWith({
    List<Todo>? todos,
  }) {
    return TodoListState(
      todos: todos ?? this.todos,
    );
  }
}

class TodoList with ChangeNotifier {
  TodoListState _state = TodoListState.init();
  TodoListState get state => _state;

  // 리스트 추가
  void addTodo(String todoDesc) {
    int? newNum;
    if(_state.todos.isEmpty){
      newNum = 1;
    } else {
      // 마지막 id 에서 1 증가
      newNum = int.parse(_state.todos.last.id)+1;
    }
    final newTodo = Todo(id: newNum.toString(),desc: todoDesc);
    final newTodos = [..._state.todos, newTodo];

    // 리스트 새로 복사&생성
    _state = _state.copyWith(todos: newTodos);
    notifyListeners();
  }

  void toggleTodo(String id) {
    final newTodos = _state.todos.map((Todo todo) {
      // 해당 리스트만 토글
      if (todo.id == id) {
        return Todo(
          id: id,
          desc: todo.desc,
          completed: !todo.completed,
        );
      }
      // 다른 리스트는 그대로 리턴
      return todo;
    }).toList();

    // 토글된 리스트 새로 복사&생성
    _state = _state.copyWith(todos: newTodos);
    notifyListeners();
  }

}

 

 

todos_screen.dart 수정

 

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) {
        if (todoDesc != null && todoDesc.trim().isNotEmpty) {
          debugPrint('CreateTodo Clicked: ${todoDesc.toString()}');
          context.read<TodoList>().addTodo(todoDesc);
          newTodoController.clear();
        }
      },
    );
  }
}
// => todo_model.dart
// 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'),
// ];
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<TodoList>().state.todos;

    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)