Skip to Content
Flet✅ Todo List

Mini Project: Todo List ✅

Xây dựng ứng dụng Todo List đầy đủ chức năng. ⏱️ 30 phút

🎯 Mục tiêu

Tạo ứng dụng Todo với các tính năng:

  • Thêm task mới
  • Đánh dấu hoàn thành/chưa hoàn thành
  • Xóa task
  • Đếm số task còn lại
  • Lọc theo trạng thái

🚀 Bắt đầu

Bước 1: Cấu trúc cơ bản

Tạo file todo_app.py:

import flet as ft @ft.observable class TodoState: items: list = [] def add(self, text: str): if text: self.items.append({ "id": len(self.items), "text": text, "done": False }) state = TodoState() def main(page: ft.Page): page.title = "Todo App" page.add(ft.Text("Todo App", size=24)) ft.run(main)

Bước 2: Form thêm task

import flet as ft @ft.observable class TodoState: items: list = [] def add(self, text: str): if text.strip(): self.items.append({ "id": len(self.items), "text": text.strip(), "done": False }) state = TodoState() @ft.component def AddTodoForm(): input_value, set_input = ft.use_state("") def add_todo(_): state.add(input_value) set_input("") return ft.Row([ ft.TextField( value=input_value, hint_text="Nhập việc cần làm...", expand=True, on_change=lambda e: set_input(e.control.value), on_submit=add_todo, ), ft.FilledButton("Thêm", icon=ft.Icons.ADD, on_click=add_todo), ]) def main(page: ft.Page): page.title = "Todo App" page.padding = 20 page.add( ft.Text("📝 Todo List", size=28, weight=ft.FontWeight.BOLD), AddTodoForm(), ) ft.run(main)

Bước 3: Hiển thị danh sách tasks

import flet as ft @ft.observable class TodoState: items: list = [] def add(self, text: str): if text.strip(): self.items.append({ "id": len(self.items), "text": text.strip(), "done": False }) def toggle(self, id: int): for item in self.items: if item["id"] == id: item["done"] = not item["done"] break def delete(self, id: int): self.items = [item for item in self.items if item["id"] != id] state = TodoState() @ft.component def AddTodoForm(): input_value, set_input = ft.use_state("") def add_todo(_): state.add(input_value) set_input("") return ft.Row([ ft.TextField( value=input_value, hint_text="Nhập việc cần làm...", expand=True, on_change=lambda e: set_input(e.control.value), on_submit=add_todo, ), ft.FilledButton("Thêm", icon=ft.Icons.ADD, on_click=add_todo), ]) @ft.component def TodoItem(item): return ft.ListTile( leading=ft.Checkbox( value=item["done"], on_change=lambda _: state.toggle(item["id"]), ), title=ft.Text( item["text"], style=ft.TextStyle( decoration=ft.TextDecoration.LINE_THROUGH if item["done"] else None, color=ft.Colors.GREY_500 if item["done"] else None, ), ), trailing=ft.IconButton( ft.Icons.DELETE, icon_color=ft.Colors.RED_300, on_click=lambda _: state.delete(item["id"]), ), ) @ft.component def TodoList(): if not state.items: return ft.Container( content=ft.Text("✨ Chưa có việc nào. Thêm task đi!", color=ft.Colors.GREY_500), padding=40, alignment=ft.alignment.center, ) return ft.Column([ TodoItem(item) for item in state.items ]) def main(page: ft.Page): page.title = "Todo App" page.padding = 20 page.add( ft.Text("📝 Todo List", size=28, weight=ft.FontWeight.BOLD), AddTodoForm(), ft.Divider(), TodoList(), ) ft.run(main)

Bước 4: Thêm bộ lọc và đếm

import flet as ft @ft.observable class TodoState: items: list = [] filter: str = "all" # all, active, completed def add(self, text: str): if text.strip(): self.items.append({ "id": len(self.items), "text": text.strip(), "done": False }) def toggle(self, id: int): for item in self.items: if item["id"] == id: item["done"] = not item["done"] break def delete(self, id: int): self.items = [item for item in self.items if item["id"] != id] def set_filter(self, f: str): self.filter = f def get_filtered_items(self): if self.filter == "active": return [i for i in self.items if not i["done"]] elif self.filter == "completed": return [i for i in self.items if i["done"]] return self.items def get_remaining_count(self): return len([i for i in self.items if not i["done"]]) state = TodoState() @ft.component def AddTodoForm(): input_value, set_input = ft.use_state("") def add_todo(_): state.add(input_value) set_input("") return ft.Row([ ft.TextField( value=input_value, hint_text="Nhập việc cần làm...", expand=True, on_change=lambda e: set_input(e.control.value), on_submit=add_todo, prefix_icon=ft.Icons.ADD_TASK, ), ft.FilledButton("Thêm", icon=ft.Icons.ADD, on_click=add_todo), ]) @ft.component def FilterTabs(): return ft.Row([ ft.Text(f"{state.get_remaining_count()} việc còn lại", color=ft.Colors.GREY_600, size=14), ft.Container(expand=True), ft.SegmentedButton( selected={state.filter}, on_change=lambda e: state.set_filter(list(e.control.selected)[0]), segments=[ ft.Segment(value="all", label=ft.Text("Tất cả")), ft.Segment(value="active", label=ft.Text("Đang làm")), ft.Segment(value="completed", label=ft.Text("Xong")), ], ), ]) @ft.component def TodoItem(item): return ft.ListTile( leading=ft.Checkbox( value=item["done"], on_change=lambda _: state.toggle(item["id"]), ), title=ft.Text( item["text"], style=ft.TextStyle( decoration=ft.TextDecoration.LINE_THROUGH if item["done"] else None, color=ft.Colors.GREY_500 if item["done"] else None, ), ), trailing=ft.IconButton( ft.Icons.DELETE_OUTLINE, icon_color=ft.Colors.RED_300, tooltip="Xóa", on_click=lambda _: state.delete(item["id"]), ), ) @ft.component def TodoList(): filtered = state.get_filtered_items() if not filtered: msg = "✨ Không có việc nào" if state.filter != "all" else "✨ Thêm việc cần làm!" return ft.Container( content=ft.Text(msg, color=ft.Colors.GREY_500, size=16), padding=40, alignment=ft.alignment.center, ) return ft.Column([TodoItem(item) for item in filtered]) @ft.component def TodoApp(): return ft.Container( content=ft.Column([ ft.Text("📝 Todo List", size=28, weight=ft.FontWeight.BOLD), AddTodoForm(), ft.Container(height=10), FilterTabs(), ft.Divider(), TodoList(), ]), padding=20, width=500, ) def main(page: ft.Page): page.title = "Todo App" page.vertical_alignment = ft.MainAxisAlignment.START page.horizontal_alignment = ft.CrossAxisAlignment.CENTER page.bgcolor = ft.Colors.GREY_100 page.add( ft.Container( content=TodoApp(), bgcolor=ft.Colors.WHITE, border_radius=10, shadow=ft.BoxShadow(blur_radius=10, color=ft.Colors.BLACK12), margin=ft.margin.only(top=40), ) ) ft.run(main)

✅ Kết quả

Ứng dụng Todo hoàn chỉnh với:

  • ✅ Thêm task mới
  • ✅ Toggle hoàn thành
  • ✅ Xóa task
  • ✅ Đếm task còn lại
  • ✅ Lọc theo trạng thái
  • ✅ UI đẹp với Card và shadows

🎯 Thử thách mở rộng

  1. Edit task - Cho phép sửa nội dung task
  2. Due date - Thêm ngày hết hạn
  3. Priority - Ưu tiên cao/trung bình/thấp
  4. Persistence - Lưu vào local storage

📝 Kiến thức ôn tập

  • @ft.observable - State toàn app
  • @ft.component - Components tái sử dụng
  • State methods - add, toggle, delete, filter
  • Conditional rendering - Hiển thị theo điều kiện

⏭️ Tiếp theo

Tuyệt! Tiếp tục với Mini Project: Calculator.

Last updated on