Nếu bạn đã từng viết Flutter app, chắc chắn bạn đã dùng Generics rồi. Mỗi lần khai báo List<String> hay Future<int>, bạn đang tận dụng sức mạnh của Generic Types đó.
Nhưng dùng generics là một chuyện; tự tạo generic class và function của riêng mình mới là lúc bạn thực sự nâng tầm kiến trúc Dart. Dù bạn đang quản lý real-time data stream cho một messaging platform, cấu trúc local storage cho một journaling app, hay xây dựng enterprise tools vững chắc — generics chính là chìa khóa để codebase của bạn luôn sạch, type-safe, và DRY (Don't Repeat Yourself).
Hãy cùng phân tích cách tạo, constrain, và làm chủ custom generics trong các Flutter project của bạn.
---
Mục lục

---
Generics là gì?
Generics cho phép bạn viết code hoạt động được với bất kỳ kiểu dữ liệu nào trong khi vẫn đảm bảo type safety nghiêm ngặt. Trong Dart, generic type được quy ước biểu diễn bằng một chữ hoa:
<T> cho Type<E> cho Element<K, V> cho Key và ValueThay vì viết một class chỉ làm việc với String hay int, bạn viết class làm việc với <T>. Kiểu dữ liệu thực tế sẽ được "cắm vào" sau khi bạn khởi tạo class.
---
Vấn đề: Code bị lặp lại
Hãy tưởng tượng bạn đang fetch dữ liệu từ một API. Bạn cần xử lý trạng thái success và error cho nhiều model khác nhau. Nếu không có generics, bạn có thể phải viết các wrapper lặp đi lặp lại như thế này:
class UserResponse {
final User? data;
final String? error;
UserResponse(this.data, this.error);
}
class MessageResponse {
final Message? data;
final String? error;
MessageResponse(this.data, this.error);
}
Điều này nhanh chóng trở nên mệt mỏi. Mỗi lần bạn thêm model mới, bạn lại phải tạo thêm một response wrapper mới.
---
Giải pháp: Generic Classes
Hãy refactor lại đoạn trên bằng Generic Type. Chúng ta có thể tạo một class ApiResponse duy nhất có thể thích nghi với bất kỳ model nào ta truyền vào.
class ApiResponse<T> {
final T? data;
final String? error;
ApiResponse({this.data, this.error});
bool get isSuccess => data != null;
}
Giờ đây, code của bạn trở nên cực kỳ reusable:
// Usage
final userResult = ApiResponse<User>(data: currentUser);
final messagesResult = ApiResponse<List<Message>>(data: chatHistory);
print(userResult.isSuccess); // true
---
Generic Functions
Bạn không chỉ giới hạn ở class; bạn có thể áp dụng generics trực tiếp lên function. Điều này rất hữu ích cho các utility function thực hiện thao tác trên collection.
Đây là một function đơn giản nhận vào một list thuộc bất kỳ type nào và trả về phần tử cuối cùng một cách an toàn:
T? safeGetLast<T>(List<T> items) {
if (items.isEmpty) return null;
return items.last;
}
void main() {
final numbers = [1, 2, 3];
final strings = ['A', 'B', 'C'];
// Dart's type inference is smart enough to figure out the type
print(safeGetLast(numbers)); // Outputs: 3
print(safeGetLast(strings)); // Outputs: C
}
---
Giới hạn Generics bằng extends
Đôi khi, tự do hoàn toàn lại không phải điều bạn muốn. Bạn có thể cần generic type <T> của mình phải có những thuộc tính nhất định. Bạn có thể constrain một generic type bằng keyword extends.
Giả sử bạn đang xây dựng một generic repository. Bạn muốn đảm bảo rằng bất kỳ object nào được truyền vào repository này đều có id.
Đầu tiên, định nghĩa một base interface:
abstract class BaseModel {
String get id;
}
class User implements BaseModel {
@override
final String id;
final String name;
User(this.id, this.name);
}
Tiếp theo, constrain generic repository của bạn:
class LocalRepository<T extends BaseModel> {
final Map<String, T> _storage = {};
void save(T item) {
// Because T extends BaseModel, Dart knows 'item' has an 'id'
_storage[item.id] = item;
print('Saved item with ID: ${item.id}');
}
T? getById(String id) => _storage[id];
}
Nếu bạn cố truyền vào một type không implement BaseModel (chẳng hạn một String thông thường), Dart compiler sẽ báo lỗi ngay, ngăn chặn crash ở runtime.
---
Điểm mấu chốt
List<int> vs List).ApiResponse<T>).<T extends BaseModel>.Kết luận
Generics trông có vẻ đáng sợ lúc đầu, nhưng chúng là công cụ nền tảng để viết Flutter application có thể scale. Bằng cách tách "Type" ra khỏi logic của bạn, bạn có thể xây dựng những service và UI component mạnh mẽ, có thể tái sử dụng.
Lần tới khi bạn thấy mình đang copy-paste một class chỉ để đổi kiểu biến, hãy tự hỏi: Liệu cái này có thể biến thành Generic không?
---
Happy coding! 🚀
Loading…