Nếu bạn đã dành thời gian trong cộng đồng Flutter, chắc hẳn bạn đã từng nghe Provider được mô tả là "giải pháp state management được khuyến nghị." Cách diễn đạt này đã trở nên phổ biến đến mức hầu hết developer chấp nhận nó mà không hề đặt câu hỏi. Nhưng tôi muốn thách thức quan điểm đó: Provider thực ra không phải là một công cụ state management. Nó là một cơ chế dependency injection. Phần state management thực sự đang được thực hiện bởi ChangeNotifier.
Hiểu được sự khác biệt này sẽ thay đổi hoàn toàn cách bạn thiết kế kiến trúc ứng dụng Flutter của mình.

Hiểu Lầm Phổ Biến
Bước vào bất kỳ codebase Flutter nào, bạn sẽ thường thấy đoạn code như thế này:
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
)
Hỏi developer đó cái gì đang quản lý state ở đây, và câu trả lời có thể sẽ là "Provider." Nhưng hãy nhìn kỹ hơn. Thứ gì đang thực sự chứa state? Thứ gì đang notify cho listeners khi state đó thay đổi? Không phải Provider — đó là ChangeNotifier.
Provider chỉ là chiếc xe tải giao hàng. ChangeNotifier mới là sản phẩm thực sự.
Provider Thực Sự Làm Gì
Công việc của Provider rất đơn giản và rõ ràng: nó làm cho các object trở nên accessible với các widget trong widget tree bên dưới nó. Đó là dependency injection. Đây là khái niệm tương tự bạn sẽ tìm thấy trong hệ thống DI của Angular, IoC container của Spring, hay Dagger trong Android development — chỉ là được điều chỉnh cho phù hợp với widget tree của Flutter.
Đây là những gì Provider cung cấp cho bạn:
// Provider injects MyService into the widget tree
Provider<MyService>(
create: (_) => MyService(),
child: MyApp(),
)
// Anywhere below, widgets can access it
final service = Provider.of<MyService>(context);
// Or with the more modern API
final service = context.read<MyService>();
Bạn có nhận ra điều gì không? MyService ở đây không cần phải stateful. Nó không cần phải notify cho ai cả. Nó có thể là một HTTP client, một logger, một configuration object, hay bất kỳ plain Dart class nào. Provider không quan tâm — nó chỉ đơn giản là deliver nó đến nơi bạn cần.
Đây là dependency injection, đơn giản và rõ ràng. Bạn đang khai báo "Tôi muốn object này có thể accessible trong phần widget tree này" và Provider xử lý việc lookup và lifecycle management.
Phần State Management Thực Sự Xảy Ra Ở Đâu
Ngay khi bạn dùng đến ChangeNotifierProvider, bạn đã bước ra ngoài core functionality của Provider và đi vào các state management primitive có sẵn của Flutter. ChangeNotifier là một phần của Flutter framework, nằm trong thư viện foundation — không phải trong package Provider.
Hãy xem xét một ví dụ "Provider state management" điển hình:
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // ← This is the actual state management
}
}
State sống trong _count. Cơ chế để thông báo cho UI "này, có gì đó vừa thay đổi" là notifyListeners(). Pattern quan sát các thay đổi và rebuild là Observer pattern, được implement bởi ChangeNotifier.
Vai trò của Provider trong bức tranh này? Nó chỉ đảm bảo rằng các widget của bạn có thể tìm thấy instance ChangeNotifier này. Chỉ vậy thôi.
Bạn có thể thay thế Provider bằng InheritedWidget, GetIt, hay thậm chí truyền ChangeNotifier xuống thủ công qua constructor, và state management của bạn vẫn hoạt động hoàn toàn tương tự. Logic state không thay đổi vì Provider không phải là thứ đang làm state management.
Minh Chứng: Provider Không Cần State Management
Để chứng minh điều này, đây là Provider đang thực sự hữu ích mà không có bất kỳ state management nào:
class ApiClient {
final String baseUrl;
ApiClient(this.baseUrl);
Future<User> fetchUser(int id) async {
// ... HTTP call logic
}
}
void main() {
runApp(
Provider<ApiClient>(
create: (_) => ApiClient('https://api.example.com'),
child: MyApp(),
),
);
}
ApiClient không có state, không có listener, không có notification. Nhưng Provider vẫn cực kỳ có giá trị ở đây — nó cho mọi widget trong tree truy cập vào một instance ApiClient duy nhất, nhất quán. Đây là dependency injection chuẩn mực.
Và State Management Không Cần Provider
Ngược lại, đây là ChangeNotifier đang thực hiện state management thực sự mà không có Provider ở đâu cả:
class CounterScreen extends StatefulWidget {
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
final counter = CounterModel();
@override
void initState() {
super.initState();
counter.addListener(_onChange);
}
void _onChange() => setState(() {});
@override
void dispose() {
counter.removeListener(_onChange);
counter.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('${counter.count}');
}
}
State đang được quản lý. Listener đang được notify. UI đang rebuild. Không có Provider nào được dùng đến. State management được xử lý hoàn toàn bởi ChangeNotifier.
Tại Sao Sự Phân Biệt Này Quan Trọng
Bạn có thể đang nghĩ: "Ừ thì, bắt bẻ ngữ nghĩa. Điều này quan trọng gì trong thực tế?" Nó quan trọng vì nhiều lý do thực tế.
Nó làm rõ kiến trúc của bạn. Khi bạn hiểu rằng Provider là DI và ChangeNotifier là state management, bạn bắt đầu đưa ra những lựa chọn kiến trúc gọn gàng hơn. Bạn có thể dùng Provider để inject các service, repository, và use case mà không có state. Bạn không còn đánh đồng "Tôi cần chia sẻ object này" với "Tôi cần state management."
Nó mở ra khả năng lựa chọn tool tốt hơn. Một khi bạn tách biệt hai mối quan tâm này, bạn nhận ra mình có thể kết hợp chúng theo nhiều cách. Dùng Provider cho DI và kết hợp với ValueNotifier, Stream, Riverpod, BLoC, hay bất kỳ phương pháp state management nào khác. Chúng không phải là đối thủ cạnh tranh của Provider — chúng là bổ sung hoặc thay thế cho ChangeNotifier.
Nó giải thích những giới hạn của Provider. Mọi người thường phàn nàn rằng Provider "không scale được" hay "trở nên lộn xộn trong các app lớn." Nhưng những phàn nàn này thường không phải về Provider — chúng là về ChangeNotifier. ChangeNotifier có những giới hạn thực sự: nó mutable, thiếu reactivity chi tiết, và có thể khuyến khích các model class phình to. Khi bạn hiểu được sự tách biệt, bạn nhận ra giải pháp không phải là từ bỏ Provider, mà là thay thế ChangeNotifier bằng thứ phù hợp hơn với nhu cầu của bạn.
Nó định hướng các con đường migration. Các team đang cân nhắc chuyển từ Provider sang Riverpod thường nghĩ rằng họ đang đổi giải pháp state management. Họ không phải — họ đang đổi cả DI lẫn state management cùng một lúc, vì Riverpod xử lý cả hai. Hiểu rõ từng phần làm gì giúp việc migration rõ ràng hơn và có chủ đích hơn.
Chính Tài Liệu Của Package Provider
Nếu bạn đọc kỹ tài liệu của package Provider, tác giả Remi Rousselet thực ra đã khá rõ ràng về điều này. Provider được mô tả là một wrapper xung quanh InheritedWidget để làm chúng reusable hơn. Nó là về việc expose các giá trị xuống widget tree — đó là dependency injection. Các class state management (ChangeNotifier, ValueNotifier, stream) là những khái niệm riêng biệt mà Provider biết cách tích hợp thông qua các specialized provider như ChangeNotifierProvider và StreamProvider.
Sự tiện lợi của ChangeNotifierProvider chỉ thành công đến mức người ta đã đánh đồng hai vai trò này với nhau.
Một Mental Model Gọn Gàng Hơn
Đây là cách tôi muốn bạn nghĩ về nó từ bây giờ. Provider trả lời câu hỏi "làm thế nào tôi đưa object này đến các widget cần nó?" Đó là câu hỏi dependency injection. ChangeNotifier, ValueNotifier, stream, và các reactive primitive khác trả lời câu hỏi "làm thế nào tôi notify cho UI khi có gì đó thay đổi?" Đó là câu hỏi state management.
Hầu hết các app cần câu trả lời cho cả hai câu hỏi, đó là lý do tại sao Provider và ChangeNotifier thường được dùng cùng nhau. Nhưng chúng đang giải quyết các vấn đề khác nhau, và việc xem chúng như một khái niệm duy nhất che khuất những gì đang thực sự xảy ra trong code của bạn.
Lời Kết
Lần tới khi bạn dùng đến ChangeNotifierProvider, hãy dành một chút thời gian để đánh giá cao những gì từng phần đang làm. Provider đang xử lý dependency injection một cách duyên dáng — đảm bảo rằng các widget của bạn có thể truy cập model mà không cần prop-drilling qua constructor. ChangeNotifier đang thực hiện state management thực sự — giữ các giá trị, nhận các mutation, và notify observer khi mọi thứ thay đổi.
Đây không chỉ là lý thuyết thuần túy. Một khi sự phân biệt này trở nên rõ ràng, bạn sẽ thấy mình viết code gọn gàng hơn, chọn tool tốt hơn cho từng công việc cụ thể, và có những cuộc trò chuyện hiệu quả hơn về kiến trúc Flutter. Bạn sẽ không còn hỏi "nên dùng Provider hay BLoC?" — một câu hỏi lẫn lộn về category — mà thay vào đó sẽ hỏi "tôi muốn quản lý state như thế nào, và tôi muốn inject dependency ra sao?" Hai quyết định riêng biệt, hai tool phù hợp.
Provider rất xuất sắc trong những gì nó thực sự làm. Chúng ta chỉ cần thành thật về điều đó là gì.
Happy Coding : )
Loading…