Flutter

TodoList App ๋งŒ๋“ค๊ธฐ

2์ฃผ๋…˜ 2024. 3. 21. 10:31
๋ฐ˜์‘ํ˜•
๐ŸšฉTodoList App ๋งŒ๋“ค๊ธฐ ๋ชฉํ‘œ
1. ํด๋” ๊ตฌ์กฐ ์žก๊ธฐ
2. main ํŒŒ์ผ ๋งŒ๋“ค์–ด ๋ณด๊ธฐ
3. Model ํด๋ž˜์Šค ์ƒ์„ฑํ•ด๋ณด๊ธฐ
4. View ๋งŒ๋“ค์–ด ๋ณด๊ธฐ
5. ViewModel ๋งŒ๋“ค์–ด ๋ณด๊ธฐ
6. view(todo_list_view.dart) ์— ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ ํ•˜๊ธฐ

 

 

1. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๊ตฌ์„ฑํ•˜๊ธฐ

2. main.dart ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

import 'package:flutter/material.dart';

void main() {
  runApp(const TodoApp());
}

class TodoApp extends StatelessWidget {
  const TodoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(title: const Text("TodoList"),),
          body: Center(
            child: Text("My Todo"),
          ),
        ),
      ),
    );
  }
}

 

import 'package:flutter/material.dart';
import 'package:my_todo_mvvm/views/todo_list_view.dart';

void main() {
  runApp(const TodoApp());
}

class TodoApp extends StatelessWidget {
  const TodoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(title: const Text("TodoList"),),
          body: TodoListView(),
        ),
      ),
    );
  }
}

 

3. Model ํด๋ž˜์Šค ์ƒ์„ฑํ•ด๋ณด๊ธฐ

/models/todo_item.dart ํŒŒ์ผ ์ƒ์„ฑํ•˜๊ธฐ

// Model
class TodoItem {
  String title;
  bool isDone;

  TodoItem({required this.title, this.isDone = false});
}

 

 

4. view ๋งŒ๋“ค์–ด ๋ณด๊ธฐ

/views/todo_list_view.dart ํŒŒ์ผ ์ƒ์„ฑํ•˜๊ธฐ (1๋‹จ๊ณ„)

import 'package:flutter/material.dart';


// View ํด๋ž˜์Šค 

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
} // end of TodoListView class


class _TodoListViewState extends State<TodoListView> {

  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: 'Enter todo item...',
              suffix: IconButton(
                icon: Icon(Icons.add),
                onPressed: () {
                 setState(() {
                   // build() ๋ฉ”์„œ๋“œ ์žฌ ํ˜ธ์ถœ
                   _controller.clear();
                 });

                },
              )
            ),
          ),


        ],
      ),
    );
  }
} // end of _TodoListViewState

 

TextEditingController๋Š” TextField ์œ„์ ฏ์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด TextField์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜, TextField์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜, TextField๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๋“ฑ ๋‹ค์–‘ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ž…๋ ฅ๊ฐ’ ์ ‘๊ทผ:
    • *TextEditingController*๋Š” _controller.text ํ”„๋กœํผํ‹ฐ๋ฅผ ํ†ตํ•ด ํ˜„์žฌ **TextField*์— ์ž…๋ ฅ๋œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ **TextField*์— ์ž…๋ ฅํ•œ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€์„œ ๋กœ์ง์— ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๊ณณ์— ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์ž…๋ ฅ๊ฐ’ ๋ณ€๊ฒฝ:
    • *_controller.text = 'newValue'*์™€ ๊ฐ™์ด **TextEditingController*์˜ text ํ”„๋กœํผํ‹ฐ๋ฅผ ํ†ตํ•ด **TextField*์˜ ๊ฐ’์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ์ž…๋ ฅ๊ฐ’ ์ดˆ๊ธฐํ™”:
    • *TextEditingController*์˜ clear ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ **TextField*์˜ ๋‚ด์šฉ์„ ์‰ฝ๊ฒŒ ์ง€์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์„ ์™„๋ฃŒํ•œ ํ›„ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  4. ์ž…๋ ฅ ๋ณ€๊ฒฝ ๊ฐ์ง€:
    • *TextEditingController*์— ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ **TextField*์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ž…๋ ฅ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ ํŠน์ • ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ 2๋‹จ๊ณ„

import 'package:flutter/material.dart';
import 'package:my_todo_mvvm/models/todo_item.dart';

// View ํด๋ž˜์Šค

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
} // end of TodoListView class

class _TodoListViewState extends State<TodoListView> {
  final TextEditingController _controller = TextEditingController();

  // ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค์–ด ๋ณด๊ธฐ
  List<TodoItem> _todoItems = [
    TodoItem(title: 'ํ”Œ๋Ÿฌํ„ฐ ๊ณต๋ถ€ํ•˜๊ธฐ', isDone: false),
    TodoItem(title: '๋‚ฎ์ž  ์ž๊ธฐ', isDone: true),
  ];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: 'Enter todo item...',
              suffix: IconButton(
                icon: Icon(Icons.add),
                onPressed: () {
                  setState(() {
                    // build() ๋ฉ”์„œ๋“œ ์žฌ ํ˜ธ์ถœ
                    _controller.clear();
                  });
                },
              ),
            ),
          ),
          //
          Expanded(
            child: ListView.builder(
              itemCount: _todoItems.length,
              itemBuilder: (context, index) {
                var item = _todoItems[index];
                // ๋‘๊ฐœ์˜ ์ธ์ˆ˜ ๊ฐ’์„ ๋ฐ›์•„์„œ ์œ„์ ฏ์„ ๋ฆฌํ„ด ์‹œํ‚ค๋ฉด ๋œ๋‹ค.
                return ListTile(
                  title: Text(item.title),
                  trailing: Checkbox(
                    value: item.isDone,
                    onChanged: (value) {
                      print("value : ${value}");
                      setState(() {
                        _todoItems[index].isDone = value ?? false;
                      });
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
} // end of _TodoListViewState

 

ViewModel ๋งŒ๋“ค๊ธฐ

 

// ViewModel
import 'package:my_todo_mvvm/models/todo_item.dart';

class TodoListViewModel {

  // ํ™”๋ฉด ์‚ฌ์šฉ๋  ๋ฐ์ดํ„ฐ
  List<TodoItem> _items = []; // private

  // get ๋ฉ”์„œ๋“œ ๋งŒ๋“ค์–ด ์ฃผ๊ธฐ
  List<TodoItem> get items => _items;

  // ๋ฆฌ์ŠคํŠธ์— TodoItem ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฉ”์„œ๋“œ ๋งŒ๋“ค๊ธฐ
  void addItem(String title) {
    _items.add(TodoItem(title: title, isDone: false));
  }

  void toggleItem(TodoItem todo) {
    todo.isDone = !todo.isDone;
  }

}

 

todo_list_view.dart ์ˆ˜์ •

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_todo_mvvm/models/todo_item.dart';
import 'package:my_todo_mvvm/view_models/todo_list_view_model.dart';

// View ํด๋ž˜์Šค

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
} // end of class

class _TodoListViewState extends State<TodoListView> {

  final TextEditingController _controller = TextEditingController();
  final TodoListViewModel listViewModel = TodoListViewModel();





  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: 'Enter Todo Item',
              suffix: IconButton(
                icon: Icon(Icons.add),
                onPressed: (){
                  setState(() {
                    listViewModel.addItem(_controller.text);
                    _controller.clear();
                  });
                },
              )
            ),
          ),
          //
          Expanded(
            child: ListView.builder(
              itemCount: listViewModel.items.length,
              itemBuilder: (context, index){
                var item = listViewModel.items[index];
                // ๋‘๊ฐœ ์ธ์ˆ˜๊ฐ’์„ ๋ฐ›์•„์„œ ์œ„์ ฏ์„ ๋ฆฌํ„ด
                return ListTile(
                  title: Text(item.title),
                  trailing: Checkbox(
                    value: item.isDone,
                    onChanged: (value) {
                      print('value : ${value}');
                      setState(() {
                        listViewModel.toggleItem(item);
                      });
                    },
                  ),
                );
            },),
          )

        ],
      ),
    );
  }
} // end of _TodoListViewState

 

 

๋ฐ˜์‘ํ˜•