Back to Blog
FlutterOOPDartGeneric

Đừng sợ <T>: giải mã generics trong Dart và Flutter

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ì?
  • Vấn đề: Code bị lặp lại
  • Giải pháp: Generic Classes
  • Generic Functions
  • Giới hạn Generics bằng 'extends'
  • Kết luận
  • 1_B-DK6g9wpUVW7o94_wLCQw.webp

    ---

    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à Value
  • Thay 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

  • Type Safety: Phát hiện lỗi ở compile-time thay vì runtime (ví dụ: List<int> vs List).
  • Reusability: Một class/function hoạt động được với nhiều type khác nhau (ví dụ: ApiResponse<T>).
  • Constraints: Giới hạn generics trong một class hierarchy cụ thể bằng <T extends BaseModel>.
  • Mẹo kiến trúc: Dùng generics cho data layer (Repositories, Services) và các state management wrapper để giữ business logic của bạn sạch sẽ và dễ scale.

    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! 🚀

    Enjoyed this read?

    8likes

    Found this helpful?

    Support my work.

    If this post saved you time or taught you something, you can buy me a coffee. Every bit keeps me writing and shipping.

    Support my work.

    If this post saved you time or taught you something, you can buy me a coffee. Every bit keeps me writing and shipping.

    TP BankVietnam

    Scan with any VN banking app

    Show QRHide QR
    Scan to support via Vietnamese bank (VietQR)Scan to pay
    PayPalGlobal

    Pay with your PayPal account

    Support via PayPal
    Show QRHide QR
    Scan to support via PayPalScan to pay
    GitHubGlobal

    Visa / Mastercard · one-time or monthly

    Sponsor on GitHub
    Show QRHide QR
    Scan to sponsor on GitHubScan to pay
    Comments

    Loading…

    END OF SHEET — LET'S BUILD

    Got a hard problem? Let's talk.