Hướng Nghiệp Dữ Liệu

Hướng dẫn xây dựng form nhập liệu với TextFormField + Validator

Được viết bởi Admin|0 lượt xem
Hướng dẫn xây dựng form nhập liệu với TextFormField + Validator

Tóm tắt nhanh

Tìm hiểu cách tạo form nhập liệu chuyên nghiệp trong Flutter với TextFormField và validation để đảm bảo dữ liệu hợp lệ.

Mục lục bài viết (39)

Form nhập liệu là phần quan trọng trong mọi ứng dụng. Bài viết này sẽ hướng dẫn bạn tạo form chuyên nghiệp với validation trong Flutter.

TextFormField cơ bản

1. Simple TextFormField

[object Object]

2. Với Controller

[object Object]

Validation cơ bản

1. Required Field

[object Object]

2. Email Validation

[object Object]

3. Password Validation

[object Object]

Form hoàn chỉnh

1. Login Form

[object Object]

Advanced Validation

1. Custom Validator

[object Object]

2. Sử dụng Custom Validator

[object Object]

Registration Form Example

[object Object]

Best Practices

1. Dispose Controllers

[object Object]

2. Use GlobalKey for Form

[object Object]

3. Show Validation on Submit

[object Object]

4. Clear Form

[object Object]

Form một bước hay nhiều bước: chia thế nào cho đúng

Cùng một lượng thông tin, nhưng cách chia bước ảnh hưởng trực tiếp tới tỷ lệ hoàn thành. Vài nguyên tắc từ thực tế làm sản phẩm:

  • Mỗi bước nên vừa một màn hình điện thoại. Nếu người dùng phải cuộn nhiều lần mới hết một bước, họ khó biết mình còn bao xa.
  • Nhóm theo ý nghĩa, không theo số lượng ô. Bước "Thông tin liên hệ" và "Thông tin khoá học" dễ hiểu hơn bước 1, bước 2, bước 3.
  • Đặt ô dễ ở đầu. Bắt đầu bằng ô dễ (tên, email) giúp người dùng "vào guồng"; những ô nặng như số căn cước hay mã số thuế nên ở cuối.
  • Chỉ hỏi những gì thực sự dùng. Mỗi ô thêm vào đều làm giảm tỷ lệ hoàn thành, và ô không ai dùng vẫn phải bảo trì.

Với form nhiều bước, điểm cần lưu ý kỹ thuật quan trọng nhất là: chỉ kiểm tra bước đang hiển thị. Nếu bạn giữ một Form cho tất cả các bước và gọi validate() trên toàn bộ, người dùng sẽ thấy lỗi ở những bước chưa tới — trải nghiệm rất khó hiểu.

[object Object]

Dùng IndexedStack giữ nguyên trạng thái của các bước đã đi qua, nên người dùng bấm "Quay lại" vẫn thấy dữ liệu cũ. Đây là chi tiết nhỏ nhưng ảnh hưởng lớn tới cảm nhận.

Trạng thái của form: bẩn, hợp lệ, đang gửi, đã gửi

Form "ngây thơ" chỉ biết nội dung các ô. Form tốt biết thêm vài trạng thái để giao diện phản hồi đúng:

  • Có thay đổi chưa lưu (dirty): cần cho việc xác nhận khi rời màn hình và cho việc tự lưu bản nháp.
  • Hợp lệ hay chưa: dùng để bật/tắt nút gửi. Lưu ý: làm mờ nút gửi khi chưa hợp lệ thường gây khó chịu, vì người dùng không biết vì sao mình không bấm được. Cách tốt hơn là để nút luôn bấm được và hiện lỗi cụ thể khi bấm.
  • Đang gửi: dùng để chặn bấm trùng và hiện tiến trình.
  • Đã gửi thành công: cần một màn hình hoặc thông báo rõ ràng, kèm bước tiếp theo.
[object Object]

Với dirty tracking, cách đơn giản là so sánh dữ liệu hiện tại với bản gốc: lưu một bản sao lúc mở form, và so sánh mỗi khi giá trị thay đổi. Nhớ chống dội phép so sánh nếu form dài.

Tự động lưu bản nháp để không mất dữ liệu

Người dùng điền form dài trên điện thoại rất dễ bị ngắt: có cuộc gọi, chuyển app, hết pin. Mất toàn bộ nội dung là lỗi trải nghiệm nghiêm trọng và cũng dễ tránh.

[object Object]

Ba điểm cần làm đúng:

  1. Chống dội khi lưu để không ghi liên tục mỗi ký tự.
  2. Xoá bản nháp sau khi gửi thành công, nếu không lần sau người dùng vào sẽ thấy dữ liệu cũ gây nhầm lẫn.
  3. Cho người dùng biết đang có bản nháp nếu khôi phục dữ liệu — ví dụ dòng chữ "Đã khôi phục nội dung bạn nhập dở" kèm nút xoá.

Trường có điều kiện: hiện và ẩn theo lựa chọn khác

Form có ô chỉ xuất hiện khi người dùng chọn một phương án (ví dụ chọn "xuất hoá đơn" mới hiện ô mã số thuế) cần xử lý cẩn thận về mặt trạng thái: ô bị ẩn nhưng dữ liệu vẫn còn.

[object Object]

Hai lỗi hay gặp ở đây:

  • Dữ liệu của ô bị ẩn vẫn được gửi lên. Hãy xoá hoặc bỏ qua giá trị khi điều kiện tương ứng không được chọn.
  • validator của ô bị ẩn vẫn chạy nếu ô đó vẫn nằm trong cây widget (chỉ bị đặt ở ngoài màn hình). Nếu bạn ẩn bằng cách khác thay vì không dựng widget, hãy bỏ điều kiện kiểm tra tương ứng.

Đồng bộ nhiều ô và xác thực chéo

Nhiều form cần các ô "nói chuyện" với nhau: chọn số lượng thì đổi tổng tiền, chọn ngày bắt đầu thì ngày kết thúc phải sau đó, nhập mật khẩu thì ô xác nhận phải khớp.

Để xác thực chéo, validator cần truy cập giá trị của ô khác — điều này dễ làm code rối, nên hãy dùng một nơi giữ dữ liệu duy nhất:

[object Object]

Chi tiết onChanged ở ô mật khẩu thứ nhất là điểm quan trọng: nếu không có nó, người dùng sửa mật khẩu cho khớp nhưng vẫn thấy lỗi cũ ở ô xác nhận, và họ tưởng mình vẫn sai.

Với các ô phụ thuộc (tổng tiền, số tiền quy đổi), hãy tính trong một hàm thuần từ dữ liệu đầu vào và cập nhật bằng ValueListenableBuilder trên controller, thay vì setState cả form mỗi ký tự.

Trải nghiệm gửi form

Phần này quyết định ấn tượng cuối cùng của người dùng:

  • Vô hiệu hoá nút gửi trong lúc xử lý và hiện vòng xoay ngay trong nút, không phải ở giữa màn hình.
  • Giữ dữ liệu khi gửi lỗi. Không bao giờ xoá form chỉ vì API trả về lỗi.
  • Hiện lỗi ở đúng chỗ. Lỗi thuộc một ô thì errorText của ô đó; lỗi chung (mất mạng, hết phiên) thì ở trên hoặc dưới form.
  • Cho biết kết quả sau khi gửi. Nếu điều hướng sang màn hình khác, hãy hiện SnackBar xác nhận ở màn hình đích — nhiều người dùng không chắc thao tác đã thành công và sẽ gửi lại.
  • Có đường thoát khi mạng chậm. Sau một khoảng chờ, hiện thêm nút "Thử lại" hoặc thông báo đang mất kết nối, thay vì để vòng xoay quay vô hạn.

Trợ năng và kiểm tra với cỡ chữ lớn

Form là nơi dễ phát hiện vấn đề trợ năng nhất, và cũng là nơi ảnh hưởng nhiều người nhất:

  • Nhãn phải luôn hiển thị. labelText của InputDecoration đủ tốt; tránh dùng hintText thay cho nhãn, vì gợi ý sẽ biến mất khi người dùng bắt đầu gõ.
  • Thông báo lỗi phải đọc được bằng trình đọc màn hình. Lỗi hiển thị bằng errorText đã được công bố ở mức trợ năng; nếu bạn tự vẽ thông báo, hãy bọc Semantics.
  • Vùng chạm đủ lớn cho checkbox điều khoản và các nút nhỏ.
  • Kiểm tra với cỡ chữ lớn nhất trong cài đặt hệ thống. Đây là bài kiểm tra nhanh nhất để phát hiện tràn khung — và trong form, tràn khung thường làm mất nút gửi.

Test form nhiều bước

Ngoài các test cơ bản, form nhiều bước cần kiểm tra thêm hai hành vi dễ sai: không cho qua bước khi dữ liệu sai, và giữ dữ liệu khi quay lại.

[object Object]

Test thứ hai đặc biệt có giá trị: nó bắt được lỗi dữ liệu bị mất khi điều hướng — lỗi rất dễ xảy ra khi bạn refactor và đổi cách dựng các bước.

Lỗi thường gặp khi làm form nhập liệu

  1. Một Form duy nhất cho tất cả các bước, khiến lỗi hiện ở bước người dùng chưa tới.
  2. Nút gửi bị vô hiệu hoá khi chưa hợp lệ mà không nói vì sao, làm người dùng bế tắc.
  3. Xoá form khi gửi lỗi, khiến người dùng phải nhập lại từ đầu.
  4. Không chặn bấm trùng, tạo ra nhiều bản ghi cho cùng một lần đăng ký.
  5. Không khôi phục bản nháp, làm mất dữ liệu khi người dùng bị ngắt giữa chừng.
  6. Gửi cả dữ liệu của ô đang bị ẩn, tạo ra dữ liệu rác trong hệ thống.
  7. Đặt ô nặng ở đầu form, làm người dùng bỏ đi trước khi thấy giá trị của việc điền form.
  8. Không xử lý bàn phím, khiến nút gửi bị che ở bước cuối — bước quan trọng nhất.

Câu hỏi thường gặp

Nên dùng Form cho từng bước hay một Form với các FormField ẩn?

Từng bước một Form là cách rõ ràng nhất và cho phép kiểm tra độc lập. Dùng một Form duy nhất chỉ nên áp dụng khi tất cả các ô quan trọng ở cùng một màn hình.

Làm sao hiển thị tiến trình nhiều bước cho đúng?

Dùng chỉ báo phần trăm hoặc dãy bước có tên. Tránh chỉ hiện số bước nếu form dài, vì người dùng khó hình dung còn bao xa; và không bao giờ cho tiến trình tự nhảy ngược khi họ quay lại sửa bước trước.

Có nên tự động chuyển bước khi bước hiện tại hợp lệ?

Không nên. Người dùng cần một hành động rõ ràng để biết mình vừa hoàn thành bước; tự chuyển dễ gây bất ngờ, nhất là khi họ vừa nhập xong bằng bàn phím.

Dữ liệu bản nháp nên lưu ở đâu?

Bộ nhớ cục bộ là đủ cho bản nháp, vì đây chỉ là dữ liệu tạm. Lưu ý không lưu những trường nhạy cảm (số thẻ, mật khẩu) vào bản nháp — ngay cả khi người dùng mong muốn được khôi phục, rủi ro bảo mật lớn hơn lợi ích.

Bộ kiểm tra dữ liệu dùng chung cho cả dự án

Khi dự án có nhiều form (đăng ký, đặt hàng, hồ sơ, thanh toán), việc mỗi màn hình tự viết lại kiểm tra là nguồn gốc của thông báo lỗi không nhất quán: chỗ nói "Email không hợp lệ", chỗ nói "Sai định dạng email". Hãy đặt bộ kiểm tra ở một thư mục dùng chung và chỉ trả về thông báo theo cùng một văn phong.

[object Object]

Hai nguyên tắc cho thông báo lỗi, dựa trên hành vi người dùng thật:

  • Nói người dùng cần làm gì, không chỉ nói họ sai. "Email không hợp lệ, ví dụ: ten@gmail.com" hữu ích hơn "Email sai".
  • Không dùng từ kỹ thuật như "dữ liệu không đúng định dạng" hay "giá trị không hợp lệ".

Kiểm tra form bằng widget test

Form là nơi tập trung logic quan trọng nhất trong màn hình, nhưng cũng là nơi dễ bị bỏ qua test nhất. Ba test dưới đây có giá trị cao nhất và viết rất nhanh:

[object Object]

Test thứ ba kiểm tra đúng thứ dễ lỗi nhất trong thực tế: chặn gửi trùng. Hãy luôn truyền lớp repository giả qua constructor để test có thể đếm số lần gọi.

Những chi tiết nhỏ tạo khác biệt lớn

Sau khi form đã chạy đúng, đây là những chỉnh sửa nhỏ mang lại cảm giác hoàn thiện rõ rệt:

  • Đưa con trỏ vào ô đầu tiên khi màn hình chỉ có một mục đích duy nhất — người dùng bắt đầu gõ ngay mà không phải chạm.
  • Đóng bàn phím khi người dùng chạm ra ngoài bằng onTapOutside, tránh tình trạng bàn phím che nút bên dưới.
  • Giữ nút hành động luôn nhìn thấy ở form dài: đặt trong bottomNavigationBar hoặc bottomSheet thay vì cuối danh sách.
  • Hiện đếm ký tự cho ô có giới hạn, vì người dùng cần biết còn bao nhiêu chỗ.
  • Không tự xoá dữ liệu người dùng đã nhập, kể cả khi họ chọn phương án khác ở ô phía trên.

Những thay đổi này không làm form "đẹp" hơn về mặt hình ảnh, nhưng làm người dùng đi hết form nhanh hơn và ít bỏ dở. Phần nền tảng kỹ thuật của từng ô nhập, gồm TextFormField, validator và inputFormatters, được trình bày ở bài Flutter Form đầy đủ.

Ba mức độ form và cách tiếp cận tương ứng

Không phải form nào cũng cần làm theo cùng một cách. Chia thành ba mức giúp bạn tránh việc kỹ thuật hoá quá mức một form đơn giản:

Mức 1 — form ngắn, một màn hình, độ 3–5 ô. Đây là trường hợp phổ biến nhất: form liên hệ, đăng ký nhận tin, đổi mật khẩu. Cách làm: một Form, một GlobalKey<FormState>, validator đơn giản, gọi API trực tiếp từ trạng thái của widget. Không cần tách lớp, không cần quản lý trạng thái phức tạp.

Mức 2 — form nhiều bước hoặc nhiều nhánh. Ví dụ đặt hàng có bước chọn sản phẩm, thông tin giao hàng và xác nhận. Ở mức này bạn cần: mỗi bước một Form riêng, dữ liệu được gom dần vào một nơi, có chỉ báo tiến trình, và có xử lý quay lại bước trước mà không mất dữ liệu.

Mức 3 — form là lõi nghiệp vụ của tính năng. Ví dụ công cụ tạo báo giá, cấu hình sản phẩm, trình soạn nội dung. Ở mức này, trạng thái của form nên được quản lý bởi một lớp riêng (provider, cubit hoặc bloc) vì cần test được, cần khôi phục bản nháp, cần theo dõi thay đổi và cần kiểm tra chéo nhiều trường. Việc dùng công cụ quản lý trạng thái ở đây là hợp lý, không phải làm quá.

Cách nhận biết bạn đang ở sai mức: dùng bloc cho form ba ô (mức 1 nhưng làm theo mức 3) khiến dự án có thêm ba file mà không giải quyết vấn đề gì; ngược lại, để toàn bộ logic của công cụ báo giá nằm trong State của một widget (mức 3 nhưng làm theo mức 1) sẽ khiến việc test và bảo trì rất khó.

Kết luận

Xây dựng form với TextFormField và Validator:

  • ✅ Validation: Đảm bảo dữ liệu hợp lệ
  • ✅ User Experience: Feedback rõ ràng
  • ✅ Reusable: Tạo validators tái sử dụng
  • ✅ Best Practices: Dispose controllers, use Form key

Tóm tắt:

  1. TextFormField: Widget nhập liệu
  2. Validator: Function kiểm tra dữ liệu
  3. Form: Wrap với Form widget
  4. GlobalKey: Quản lý form state
  5. Controllers: Quản lý text input

Tạo form chuyên nghiệp sẽ cải thiện trải nghiệm người dùng! 🚀


Học Flutter bài bản theo lộ trình

Nếu bạn muốn học có người sửa bài thay vì tự ghép tài liệu, Flutter Hướng Nghiệp đào tạo theo 3 cấp:

Lớp online trực tiếp qua Zoom/Google Meet, có bản ghi lại — không bắt buộc đến cơ sở.
Xem lịch khai giảng hoặc so sánh 5 cách học lập trình mobile nếu còn phân vân.