aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter Dismissible: xác nhận rồi xóa đúng dòng theo ID

Suong Nguyen by Suong Nguyen
31/10/2026
in Flutter
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Một hàng đã trượt khỏi màn hình nhưng vẫn nằm trong danh sách dữ liệu; ở lần build sau, ứng dụng có thể gặp lỗi vì Dismissible đã bị dismiss vẫn còn trong cây. Một lỗi khác là xóa theo index cũ, làm mất nhầm hàng sau khi danh sách thay đổi.

Bài này dùng Flutter Material và Dismissible, đối chiếu tài liệu ngày 31/10/2026. Mẫu chỉ quản lý danh sách trong RAM. Chưa chạy Flutter build, widget test hoặc thao tác vuốt trên thiết bị; phần kiểm tra dưới đây là các bước cần thực hiện trong dự án của bạn.

1. Hai callback có hai trách nhiệm

confirmDismiss trả quyết định cho phép thao tác tiếp tục. Kết quả false hoặc null đưa hàng về vị trí ban đầu. onDismissed chạy sau khi hàng đã hoàn tất phần dismiss và là nơi ví dụ cập nhật danh sách.

Không xóa dữ liệu ngay lúc mở dialog, vì người dùng còn có thể chọn Hủy hoặc đóng dialog. Cũng không chỉ trả true rồi bỏ quên cập nhật collection. Trong một danh sách, mỗi Dismissible phải có key phân biệt với những hàng khác.

2. Mẫu chạy trong ứng dụng Material

Dùng đoạn dưới trong lib/main.dart của một project Flutter có sẵn. Mỗi task có ID cố định và duy nhất. Nội dung chỉ là việc học mẫu, không gửi yêu cầu xóa lên server.

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: TaskPage()));

class TaskItem {
  const TaskItem(this.id, this.title);
  final String id;
  final String title;
}

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

  @override
  State<TaskPage> createState() => _TaskPageState();
}

class _TaskPageState extends State<TaskPage> {
  final tasks = <TaskItem>[
    const TaskItem('read', 'Đọc tài liệu'),
    const TaskItem('write', 'Viết ví dụ'),
    const TaskItem('review', 'Rà kết quả'),
  ];

  Future<bool> confirm(TaskItem task) async {
    final result = await showDialog<bool>(
      context: context,
      builder: (dialogContext) => AlertDialog(
        title: const Text('Xóa công việc?'),
        content: Text(task.title),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(dialogContext, false),
            child: const Text('Hủy'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(dialogContext, true),
            child: const Text('Xóa'),
          ),
        ],
      ),
    );
    return mounted && result == true;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Công việc local')),
      body: tasks.isEmpty
          ? const Center(child: Text('Đã hết công việc'))
          : ListView(
              children: [
                for (final task in tasks)
                  Dismissible(
                    key: ValueKey(task.id),
                    direction: DismissDirection.endToStart,
                    confirmDismiss: (_) => confirm(task),
                    onDismissed: (_) {
                      if (!mounted) return;
                      setState(() {
                        tasks.removeWhere((item) => item.id == task.id);
                      });
                    },
                    background: Container(
                      color: Colors.red,
                      alignment: AlignmentDirectional.centerEnd,
                      padding: const EdgeInsets.all(16),
                      child: const Icon(Icons.delete, color: Colors.white),
                    ),
                    child: ListTile(title: Text(task.title)),
                  ),
              ],
            ),
    );
  }
}

task được giữ theo từng vòng lặp và callback xóa dựa trên ID đó. Không suy ra ID từ vị trí hiển thị tại thời điểm callback kết thúc. Điều kiện mounted sau dialog tránh cho phép thao tác tiếp tục khi page không còn trong cây.

3. Kiểm tra cả Hủy, đóng ngoài và Xóa

Vuốt hàng giữa rồi chọn Hủy: danh sách vẫn có đủ ba mục. Thử vuốt lại và chạm ngoài dialog: kết quả null cũng phải giữ dữ liệu. Sau đó chọn Xóa và kiểm tra chỉ task có ID tương ứng biến mất.

Xóa hết từng mục để quan sát empty state. Thử danh sách đã được sắp xếp theo thứ tự khác và kiểm tra quyết định vẫn dựa trên ID. Với giao diện RTL, endToStart đi theo hướng đọc; không mô tả nó là luôn vuốt sang trái trên mọi locale.

Mẫu đã được rà theo hợp đồng callback trong tài liệu, nhưng các bước gesture và animation này chưa được chạy tự động. Khi đưa vào dự án, lưu phiên bản flutter --version, nền tảng và các bước tái hiện nếu có lỗi.

Lỗi thường gặp và giới hạn

Nếu hàng đã dismiss vẫn tồn tại, kiểm tra collection được dùng để build có thực sự được cập nhật trong onDismissed. Nếu xóa nhầm, tìm index đã bị giữ trong callback hoặc ID bị trùng. Dùng key theo index khiến định danh thay đổi khi hàng trước bị xóa.

Không dùng UniqueKey() mới ở mỗi build để che lỗi định danh. Hãy sửa mô hình dữ liệu để mỗi mục có ID ổn định. Key hỗ trợ framework nhận diện widget; ID vẫn cần được dùng đúng trong thao tác dữ liệu.

Ví dụ chưa xử lý xóa qua mạng, undo hoặc đồng bộ đa thiết bị. Với dữ liệu từ server, cần thiết kế thời điểm chờ kết quả, báo lỗi và khả năng khôi phục trước khi cho hàng biến mất vĩnh viễn. Đừng thêm một request bất đồng bộ vào onDismissed rồi mặc định animation hoàn tất là dữ liệu đã được lưu.

Nguồn tham khảo

  • Flutter: Dismissible
  • Flutter: confirmDismiss
  • Flutter: showDialog và kết quả Future

Tags: DismissibleFlutterQuản lý state
SummarizeShare234
Suong Nguyen

Suong Nguyen

Related Stories

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter SafeArea: minimum và MediaQuery padding không phải cộng tay

by Suong Nguyen
01/11/2026
0

Đặt SafeArea đúng cấp, hiểu minimum và lý do MediaQuery.padding thay đổi trong subtree đã được xử lý inset.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter IndexedStack: giữ state riêng khi chuyển giữa hai tab

by Suong Nguyen
30/10/2026
0

Tạo hai bộ đếm trong hai tab để kiểm tra state được giữ trong cây widget và hiểu chi phí của việc giữ các trang sống.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter LayoutBuilder: chọn layout theo vùng cha thay vì toàn cửa sổ

by Suong Nguyen
29/10/2026
0

Đặt một component vào vùng rộng 360 logical pixel để thấy vì sao kích thước cửa sổ không thay thế constraints của cha.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter showDialog: nhận kết quả xác nhận và kiểm tra context sau await

by Suong Nguyen
28/10/2026
0

Trả bool từ dialog qua Navigator.pop, phân biệt false với null và chỉ cập nhật giao diện khi context gọi vẫn còn mounted.

Next Post
Minh họa pipeline CI/CD từ commit đến deploy cho team nhỏ

Git merge-base --is-ancestor: nhánh đã chứa commit này chưa?

0 0 votes
Article Rating
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • React Native useColorScheme: đổi theme mà không cache màu từ module
  • Return trong finally có thể che cả return lẫn lỗi ban đầu
  • Android Intent extra: thiếu dữ liệu khác với giá trị 0

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native
  • Uncategorized

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

wpDiscuz
0
0
Would love your thoughts, please comment.x
()
x
| Reply