Null Safety là cơ chế phân biệt rõ ràng giữa kiểu dữ liệu có thể chứa null (Nullable) và kiểu dữ liệu không thể chứa null (Non-nullable) ngay ở mức ngôn ngữ lập trình, giúp trình biên dịch phát hiện và bắt lỗi liên quan đến null ngay trong quá trình viết mã thay vì để ứng dụng sụp đổ khi đang chạy .

Trong Dart, nguyên tắc mặc định là Non-nullable by default, nghĩa là một biến khi được khai báo sẽ không được phép chứa giá trị null, trừ khi lập trình viên khai báo rõ rằng biến đó có thể null.
Ví dụ khả năng chứa giá trị null String và String?
String (Non-nullable – Không được null)
Biến này bắt buộc phải có giá trị chuỗi ngay từ khi khởi tạo và chắc chắn 100% không bao giờ bị null. Nếu bạn cố tình gán null hoặc không gán giá trị cho nó, Dart sẽ báo lỗi ngay khi viết code.
String name = "Anh"; // Hợp lệ
String name = null; // Lỗi ngay lập tức: A value of type 'Null' can't be assigned to a variable of type 'String'.
String? (Nullable – Có thể null)
Biến này có thể chứa một chuỗi chữ bình thường hoặc chứa giá trị null (tức là chưa có dữ liệu/rỗng). Dấu ? chính là chiếc “nhãn cảnh báo” nói với Dart rằng: Biến này có nguy cơ rỗng đấy! Bạn có thể gán chuỗi hoặc gán null tùy ý. Nhưng vì nó có nguy cơ null, Dart sẽ bắt bạn phải kiểm tra an toàn trước khi truy cập các thuộc tính/phương thức của nó.
String? nickname = "Anh"; // Hợp lệ
String? nickname = null; // Hợp lệ
// Khi sử dụng:
print(nickname.length); // Lỗi! Dart không cho gọi trực tiếp vì nickname có thể null.
print(nickname?.length); // Hợp lệ! Dùng ?. để truy cập an toàn.
Dart áp dụng hai nguyên tắc chính để đảm bảo an toàn:
Tất cả các kiểu dữ liệu (int, String, List, CustomClass…) mặc định đều là Non-nullable (không thể chứa giá trị null). Nếu bạn cố gán null cho một biến kiểu này, Dart sẽ báo lỗi ngay khi code. Muốn một biến được phép nhận giá trị null, bạn phải thêm toán tử (?; !; ??;…) vào sau tên kiểu dữ liệu.
Fully sound (Hoàn toàn an toàn): Nếu một biến có kiểu dữ liệu là Non-nullable (ví dụ int, string,…), Dart cam kết biến đó không bao giờ chứa null tại bất kỳ thời điểm nào khi chương trình chạy.
Các toán tử quan trọng trong Null Safety
Toán tử ? (Safe call / Null-aware access): Gọi phương thức hoặc truy cập thuộc tính của một biến có thể null mà không gây sập app. Nếu biến đó là null, kết quả trả về sẽ là null thay vì văng lỗi.
int? length = name?.length;
Toán tử ! (Null assertion): Dùng để khẳng định chắc chắn với trình biên dịch rằng biến đó không phải là null tại thời điểm gọi (nếu thực sự là null, app vẫn sẽ báo lỗi runtime).
print(nickname!.length);
Toán tử ?? (If-null operator): Cung cấp giá trị mặc định nếu biến ở vế trái là null.
String displayName = nickname ?? "Khách vãng lai";
Toán tử ??= (Null-aware assignment): Chỉ gán giá trị mới nếu biến hiện tại đang là null.
String? country;
country ??= "Vietnam";
Từ khóa late
Dùng late khi bạn muốn khai báo một biến non-nullable nhưng chưa muốn khởi tạo ngay giá trị cho nó (sẽ gán sau trước khi dùng). Nếu bạn truy cập vào biến late trước khi nó được gán giá trị, Dart sẽ báo lỗi lúc chạy.
late String description;
void main() {
description = "Học Flutter rất thú vị";
print(description);
}
late final
late final là sự kết hợp giữa tính năng trì hoãn khởi tạo của late và tính chất chỉ gán giá trị một lần duy nhất của final.
Cơ chế hoạt động:
- late: Cho phép khai báo biến non-nullable mà chưa cần gán giá trị ngay.
- final: Đảm bảo biến chỉ được gán giá trị đúng 1 lần duy nhất, sau đó không thể thay đổi.
Khi nào nên dùng late final?
- Dùng cho các thuộc tính cần khởi tạo sau (ví dụ: sau khi widget đã mount, hoặc sau khi gọi một hàm async trong lifecycle), nhưng dữ liệu đó cố định và không bao giờ thay đổi trong suốt vòng đời của đối tượng (như AnimationController, StreamController, hoặc ID người dùng).
Từ khóa required trong Constructor
Trong Dart, khi làm việc với Named Parameters (tham số có tên đặt trong {}), các tham số mặc định sẽ là optional (tùy chọn) và có thể null.
Nếu thuộc tính đó là Non-nullable nhưng không có giá trị mặc định, bạn bắt buộc phải dùng từ khóa required để ép buộc người gọi phải truyền giá trị khi khởi tạo đối tượng.
class User {
final String name; // Non-nullable -> Bắt buộc dùng required
final String? bio; // Nullable -> Có thể truyền hoặc không
User({
required this.name,
this.bio,
});
}
Cơ chế Type Promotion
Dart sở hữu trình phân tích code rất thông minh. Khi bạn kiểm tra điều kiện if (variable != null), Dart sẽ tự động hiểu rằng bên trong khối lệnh if đó, biến này chắc chắn không còn null nữa.
Nhờ đó, bạn có thể gọi trực tiếp các thuộc tính/phương thức mà không cần dùng ! hay ?..
void printNameLength(String? name) {
if (name != null) {
// Trong đây, Dart tự động hiểu name đã là 'String' (Non-nullable)
print(name.length); // Không cần name!.length hay name?.length
}
}
Lợi ích khi sử dụng Null Safety
Việc áp dụng Null Safety mang lại nhiều giá trị thiết thực cho cả lập trình viên và hiệu năng của ứng dụng:
Giảm thiểu lỗi Crash App lúc Runtime: Null Safety chuyển hầu hết các lỗi liên quan đến null từ lúc ứng dụng đang chạy (Runtime Error) thành lỗi ngay khi viết code (Compile-time Error). Bạn sẽ loại bỏ được lỗi kinh điển The method was called on null – nguyên nhân hàng đầu gây sập ứng dụng Flutter.
Code rõ ràng và thể hiện rõ ý đồ (Self-documenting Code):
Khi nhìn vào kiểu dữ liệu, đồng nghiệp hoặc chính bạn trong tương lai sẽ hiểu ngay bản chất dữ liệu:
- String => Chắc chắn luôn có dữ liệu, không cần mất công check null.
- String? => Có thể thiếu dữ liệu, bắt buộc phải xử lý trường hợp null trước khi dùng.
Tối ưu hóa hiệu năng và dung lượng App (Sound Null Safety): Vì Dart cam kết biến Non-nullable không bao giờ chứa null khi chạy, trình biên dịch (Compiler) có thể loại bỏ các bước kiểm tra null thừa thãi ở tầng mã máy. Điều này giúp chương trình chạy nhanh hơn và file cài đặt (APK/IPA) nhẹ hơn.
Tăng tốc độ phát triển nhờ sự hỗ trợ của IDE: Các công cụ như VS Code hay Android Studio sẽ lập tức gạch chân đỏ cảnh báo nếu bạn quên xử lý null hoặc truyền sai kiểu dữ liệu. Bạn có thể sửa lỗi ngay lập tức thay vì tốn thời gian test app mới phát hiện ra.
Dễ dàng bảo trì và làm việc nhóm: Trong các dự án lớn có nhiều người cùng làm, Null Safety đóng vai trò như một “hợp đồng dữ liệu”. Thành viên khác khi gọi hàm hay class của bạn sẽ biết chính xác tham số nào bắt buộc truyền (required), tham số nào có thể bỏ trống mà không cần phải đọc lại toàn bộ code bên trong.
Các sai lầm thường gặp khi dùng Null Safety
Lạm dụng toán tử !: Khi thấy IDE báo lỗi nullable, nhiều người có thói quen chèn ! để “tắt cảnh báo”. Nếu thực tế dữ liệu trả về bị null, ứng dụng vẫn sẽ crash khi chạy (runtime error). Hãy ưu tiên kiểm tra bằng if (x != null), dùng ?. hoặc ??.
Khai báo ? tràn lan: Không nên gắn ? cho mọi biến. Chỉ dùng ? cho những dữ liệu thực sự có rủi ro thiếu (API optional, form chưa nhập…). Nếu dữ liệu bắt buộc phải có, hãy giữ kiểu Non-nullable để code chặt chẽ.
Lạm dụng late: Dùng late nghĩa là bạn tự chịu trách nhiệm gán giá trị trước khi truy cập. Nếu quên gán, ứng dụng sẽ văng lỗi LateInitializationError lúc runtime.
Kết luận
Null Safety không chỉ là một tập hợp các cú pháp (?, !, late, required), mà là tư duy lập trình an toàn của Dart.
Nhờ nguyên tắc Non-nullable by default và cơ chế Fully sound, Dart giúp lập trình viên kiểm soát triệt để nguy cơ lỗi null ngay từ khâu viết mã. Việc nắm vững và áp dụng đúng các toán tử, từ khóa cũng như cơ chế Type Promotion sẽ giúp ứng dụng Flutter của bạn ít crash hơn, code rõ ràng và dễ bảo trì hơn rất nhiều.





Khoá học lập trình game con rắn cho trẻ em




