Back to Blog
FlutterDependency InjectionGet_it

Nâng tầm Flutter architecture: làm chủ dependency injection với get_it và injectable

Nếu bạn đã build Flutter app được một thời gian, chắc hẳn bạn đã từng trải qua giai đoạn "spaghetti code". Đó là lúc UI widget bị tightly coupled với business logic, còn business logic thì đang trực tiếp instantiate API client và database.

Khi bạn thử viết unit test hoặc đổi sang database khác, mọi thứ vỡ vụn.

Giải pháp? Dependency Inversion* và *Dependency Injection (DI). Trong hệ sinh thái Flutter, cặp đôi vàng để xử lý vấn đề này là get_it kết hợp với injectable. Hãy cùng phân tích những khái niệm này có nghĩa gì và cách implement chúng để viết Flutter app sạch hơn, scalable hơn.

1_eiijD7qUUP24wWPr8LnMqQ.webp

Mục Lục

  • 🧠 Lý Thuyết: Dependency Inversion vs. Injection
  • - 1. Dependency Inversion Principle (DIP) - 2. Dependency Injection (DI)
  • 🛠 Nhân Vật Chính: get_it & injectable
  • 🚀 Hướng Dẫn Từng Bước
  • - Bước 1: Thêm Dependencies - Bước 2: Tạo Abstraction - Bước 3: Tạo Concrete Implementation - Bước 4: Cấu Hình Injection - Bước 5: Chạy Build Runner - Bước 6: Khởi Tạo và Sử Dụng!
  • 🎯 Tại Sao Cách Này Mạnh Đến Vậy?
  • Tổng Kết
  • ---

    🧠 Lý Thuyết: Dependency Inversion vs. Injection

    Trước khi đi vào code, hãy làm rõ thuật ngữ. Nghe có vẻ giống nhau, nhưng chúng đóng vai trò khác nhau trong SOLID principles.

    1. Dependency Inversion Principle (DIP)

    Chữ "D" trong SOLID. Nguyên tắc này nói hai điều:

  • High-level module không được phụ thuộc vào low-level module. Cả hai đều phải phụ thuộc vào abstraction (interface).

  • Abstraction không được phụ thuộc vào chi tiết cụ thể. Chi tiết cụ thể phải phụ thuộc vào abstraction.
  • Nói thẳng ra: Đừng để UI của bạn phụ thuộc trực tiếp vào FirebaseAuthService. Thay vào đó, hãy để UI phụ thuộc vào một abstract AuthService. Như vậy, nếu ngày mai bạn chuyển từ Firebase sang Supabase, UI không cần thay đổi gì cả.

    2. Dependency Injection (DI)

    DI là kỹ thuật được dùng để đạt được Dependency Inversion Principle. Thay vì một class tự tạo ra dependency của mình (ví dụ: final api = ApiClient();), dependency được "inject" vào class từ bên ngoài, thường thông qua constructor.

    ---

    🛠 Nhân Vật Chính: get_it & injectable

    Mặc dù bạn có thể truyền dependency thủ công qua widget tree, nhưng cách đó rất nhanh trở nên mệt mỏi. Đây là lúc các Flutter package phát huy tác dụng.

  • get_it: Một Service Locator cực kỳ hiệu quả. Nó hoạt động như một registry trung tâm nơi bạn có thể register các class và lấy chúng ra từ bất kỳ đâu trong app mà không cần context.
  • injectable: Một code generator cho get_it. Việc register hàng chục dependency thủ công trong get_it sẽ tạo ra một file cấu hình khổng lồ, lộn xộn. injectable cho phép bạn dùng các annotation đơn giản (@injectable, @singleton) trên class của mình, và nó sẽ tự viết toàn bộ boilerplate cho get_it!
  • ---

    🚀 Hướng Dẫn Từng Bước

    Hãy xây dựng một ví dụ đơn giản: Một app lấy dữ liệu thời tiết. Chúng ta muốn UI phụ thuộc vào một abstraction (WeatherRepository), và muốn injectable tự động wire up concrete implementation (WeatherRepositoryImpl).

    Bước 1: Thêm Dependencies

    Thêm phần sau vào pubspec.yaml:

    dependencies:
      get_it: ^7.6.4
      injectable: ^2.3.2
    
    dev_dependencies:
      injectable_generator: ^2.4.1
      build_runner: ^2.4.6
    
    Luôn kiểm tra pub.dev để biết phiên bản mới nhất.

    Bước 2: Tạo Abstraction

    Tạo một abstract class cho repository. Đây là thứ mà code high-level của bạn (như Bloc, Provider, hoặc ViewModel) sẽ giao tiếp.

    // weather_repository.dart
    abstract class WeatherRepository {
      Future<String> getCurrentWeather(String city);
    }
    

    Bước 3: Tạo Concrete Implementation

    Bây giờ, tạo implementation thực tế. Đây là nơi sức mạnh của injectable thể hiện. Chúng ta dùng annotation @LazySingleton và chỉ định nó bind vào abstract WeatherRepository.

    // weather_repository_impl.dart
    import 'package:injectable/injectable.dart';
    import 'weather_repository.dart';
    
    @LazySingleton(as: WeatherRepository)
    class WeatherRepositoryImpl implements WeatherRepository {
      
      // If this class had dependencies (like a network client), 
      // you would inject them via the constructor here!
      
      @override
      Future<String> getCurrentWeather(String city) async {
        // Imagine an API call here
        await Future.delayed(const Duration(seconds: 1));
        return "Sunny and 25°C in $city";
      }
    }
    

    Bước 4: Cấu Hình Injection

    Tạo một file tên injection.dart. Đây là entry point cho get_itinjectable.

    // injection.dart
    import 'package:get_it/get_it.dart';
    import 'package:injectable/injectable.dart';
    import 'injection.config.dart'; // This file will be generated
    
    final getIt = GetIt.instance;
    
    @InjectableInit(
      initializerName: 'init', // default
      preferRelativeImports: true, // default
      asExtension: true, // default
    )
    void configureDependencies() => getIt.init();
    

    Bước 5: Chạy Build Runner

    Bây giờ, yêu cầu Flutter generate code wiring. Chạy lệnh này trong terminal:

    dart run build_runner build --delete-conflicting-outputs
    
    Dùng watch thay vì build nếu bạn muốn nó tự động generate mỗi khi bạn lưu file.

    Bước 6: Khởi Tạo và Sử Dụng!

    Cuối cùng, khởi tạo dependency trong main.dart trước khi chạy app.

    // main.dart
    import 'package:flutter/material.dart';
    import 'injection.dart';
    
    void main() {
      // Initialize dependencies
      configureDependencies(); 
      runApp(const MyApp());
    }
    

    Bây giờ, ở bất kỳ đâu trong app (như trong Cubit, Bloc, hoặc thậm chí trực tiếp trong widget), bạn có thể lấy dependency ra như sau:

    // Fetching the dependency via the abstraction!
    final weatherRepo = getIt<WeatherRepository>();
    
    // Use it
    final weather = await weatherRepo.getCurrentWeather('Tokyo');
    

    ---

    🎯 Tại Sao Cách Này Mạnh Đến Vậy?

  • Không Boilerplate: injectable xử lý toàn bộ đoạn code getIt.registerLazySingleton<WeatherRepository>(() => WeatherRepositoryImpl()); thay cho bạn.
  • Testability: Trong unit test, bạn có thể dễ dàng mock WeatherRepository và register mock đó vào get_it. Business logic của bạn sẽ không biết sự khác biệt.
  • Clean Architecture: Các tầng UI và State Management hoàn toàn decoupled khỏi tầng network và database.
  • ---

    Tổng Kết

    Làm chủ get_itinjectable có thể tốn một vài tiếng để ngấm hoàn toàn, nhưng nó mang lại lợi ích lâu dài cho cả vòng đời dự án. Nó buộc bạn phải tư duy theo abstraction, giúp codebase trở nên modular, dễ test, và cực kỳ chuyên nghiệp.

    Happy coding! 🚀

    Enjoyed this read?

    3likes

    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.