Nguyễn Đình Cường
0 comment
09 Nov, 2026
Flutter cho phép bạn viết một lần và chạy trên Android, iOS, web và máy tính. Bài này hướng dẫn bạn tạo ứng dụng ghi chú công việc đầu tiên, giúp hiểu ba khái niệm lõi: widget, trạng thái và danh sách cuộn.
Cài Flutter SDK theo hướng dẫn chính thức cho hệ điều hành của bạn, cài Android Studio hoặc VS Code kèm plugin Flutter, rồi chạy lệnh kiểm tra sức khỏe cài đặt:
flutter doctorLệnh này liệt kê những gì còn thiếu (Android SDK, giấy phép, thiết bị). Hãy xử lý lần lượt các mục báo lỗi. Sau đó tạo dự án:
flutter create todo_app
cd todo_app
flutter runBạn có thể chạy trên máy ảo Android, điện thoại thật cắm cáp, hoặc chọn thiết bị Chrome để chạy bản web cho nhanh.
Trong Flutter, nút bấm, văn bản, khoảng cách, bố cục, thậm chí cả ứng dụng đều là widget, và widget được lồng vào nhau thành cây. Có hai loại chính. StatelessWidget không có trạng thái thay đổi. StatefulWidget có trạng thái, và khi bạn gọi setState thì giao diện được dựng lại với dữ liệu mới.
Mở lib/main.dart, xóa toàn bộ nội dung mặc định và viết:
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(
title: 'Việc cần làm',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const TodoPage(),
);
}
}class Todo {
Todo(this.title, {this.done = false});
final String title;
bool done;
}
class TodoPage extends StatefulWidget {
const TodoPage({super.key});
@override
State<TodoPage> createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
final List<Todo> _todos = [];
final TextEditingController _controller = TextEditingController();
void _addTodo() {
final text = _controller.text.trim();
if (text.isEmpty) return;
setState(() {
_todos.add(Todo(text));
_controller.clear();
});
}
void _toggle(int index, bool? value) {
setState(() => _todos[index].done = value ?? false);
}
void _remove(int index) {
setState(() => _todos.removeAt(index));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Việc cần làm')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Nhập công việc...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 8),
FilledButton(onPressed: _addTodo, child: const Text('Thêm')),
],
),
),
Expanded(
child: _todos.isEmpty
? const Center(child: Text('Chưa có công việc nào'))
: ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final todo = _todos[index];
return ListTile(
leading: Checkbox(
value: todo.done,
onChanged: (v) => _toggle(index, v),
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.done
? TextDecoration.lineThrough
: null,
),
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _remove(index),
),
);
},
),
),
],
),
);
}
}setState để Flutter biết cần vẽ lại.dispose khi không dùng nữa để tránh rò rỉ bộ nhớ.Khi ứng dụng đang chạy, sửa code rồi nhấn r trong terminal (hoặc lưu file trong IDE) để thấy thay đổi ngay mà không mất trạng thái hiện tại. Nhấn R cho hot restart khi cần khởi động lại trạng thái. Tính năng này giúp thử nghiệm giao diện cực nhanh.
shared_preferences hoặc cơ sở dữ liệu SQLite.darkTheme.Với khoảng 100 dòng Dart, bạn đã có ứng dụng có thể thêm, đánh dấu và xóa công việc. Khi nắm được widget và trạng thái, phần còn lại của Flutter chủ yếu là học thêm các widget và mẫu thiết kế.
Nếu bạn muốn học phát triển ứng dụng di động theo lộ trình rõ ràng, hãy xem các khóa học tại codebem.com.
Nguyễn Đình Cường
0 comment