Provider Dependency Injection
Dependency injection is a fundamental principle in MVVM architecture. It enables better separation of concerns, improves testability, and makes your codebase more maintainable. In Flutter, the provider package is widely used to implement dependency injection and manage state.
When working with the provider package in a Flutter MVVM setup, you frequently need to access your ViewModel from the View. There are several approaches to do this, each affecting widget rebuilds and performance differently.
This guide covers the three most common techniques for accessing your ViewModel:
Provider.of<T>(context)context.read<T>()context.watch<T>()
Provider.of<T>(context)
final viewModel = Provider.of<AuthViewModel>(context, listen: false);
viewModel.login(email, password);Use Case
- To access methods or values in your ViewModel.
- You can choose whether the widget should rebuild using the
listenparameter.
Rebuild behavior
| listen | Effect |
|---|---|
true (default) | Widget rebuilds when the model updates |
false | No rebuild on model updates |
Recommended
Use with listen: false when you're only calling a method or accessing data without needing the UI to update.
context.read<T>()
context.read<AuthViewModel>().logout();Use Case:
- Best for calling methods or retrieving data without triggering a rebuild.
- Equivalent to
Provider.of<T>(context, listen: false), but more concise.
Rebuild behavior:
- ❌ No rebuild of the widget.
Recommended:
Use inside event handlers like button presses where you don’t need the widget to react to ViewModel changes.
context.watch<T>()
final isLoggedIn = context.watch<AuthViewModel>().isLoggedIn;Use Case
- Use this when you want the UI to automatically update when the ViewModel’s value changes.
Rebuild behavior:
- ✅ Widget rebuilds every time the watched property changes.
Recommended:
Use in the build method or widgets that should dynamically respond to state changes.
Comparison
| Method | Rebuilds Widget? | Use Case |
|---|---|---|
Provider.of<T>(context) | ✅ / ❌ (based on listen) | General-purpose access |
context.read<T>() | ❌ No | Call methods / read values without rebuild |
context.watch<T>() | ✅ Yes | Automatically update UI on model changes |
Example
class LoginButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isLoading = context.watch<AuthViewModel>().isLoading;
return ElevatedButton(
onPressed: isLoading
? null
: () {
context.read<AuthViewModel>().login();
},
child: isLoading
? CircularProgressIndicator()
: Text('Login'),
);
}
}Summary
Choosing the right approach depends on what you're trying to do:
- Use
context.readfor method calls (e.g. insideonPressed) - Use
context.watchfor UI that reacts to changes - Use
Provider.ofif you need more control, but preferread/watchfor clarity