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.

Mục Lục
---
🧠 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:
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
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_it và injectable.
// 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
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?
injectable xử lý toàn bộ đoạn code getIt.registerLazySingleton<WeatherRepository>(() => WeatherRepositoryImpl()); thay cho bạn.WeatherRepository và register mock đó vào get_it. Business logic của bạn sẽ không biết sự khác biệt.---
Tổng Kết
Làm chủ get_it và injectable 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! 🚀
Loading…