Answer
Overview
MVP (Model-View-Presenter) is an architectural pattern derived from MVC. It improves on MVC by making the View passive and completely decoupling the Presenter from any Android/Flutter UI framework, making it highly testable.
The Three Components
| Component | Role | Responsibility |
|---|---|---|
| Model | Data layer | Business logic, data fetching, state |
| View | UI layer | Renders UI; delegates all logic to Presenter |
| Presenter | Logic layer | Retrieves data from Model; updates View via interface |
Key Difference from MVC
In MVC, the Controller can directly manipulate the View. In MVP, the Presenter never directly touches the View — it only communicates through an interface (contract).
textUser Input → View → Presenter → Model ↑ ↓ View Interface (IView)
MVP in Flutter (Example)
1. Define the Contract (Interface)
dart// View interface — what the Presenter can tell the View to do abstract class IUserView { void showLoading(); void hideLoading(); void showUser(UserModel user); void showError(String message); }
2. Model
dartclass UserModel { final String name; final String email; UserModel({required this.name, required this.email}); } class UserRepository { Future<UserModel> fetchUser(int id) async { // Simulate API call await Future.delayed(Duration(seconds: 1)); return UserModel(name: 'Alice', email: 'alice@example.com'); } }
3. Presenter
dartclass UserPresenter { final IUserView _view; final UserRepository _repository; UserPresenter(this._view, this._repository); Future<void> loadUser(int id) async { _view.showLoading(); try { final user = await _repository.fetchUser(id); _view.showUser(user); } catch (e) { _view.showError('Failed to load user: $e'); } finally { _view.hideLoading(); } } }
4. View (Flutter Widget)
dartclass UserScreen extends StatefulWidget { _UserScreenState createState() => _UserScreenState(); } class _UserScreenState extends State<UserScreen> implements IUserView { late UserPresenter _presenter; bool _isLoading = false; UserModel? _user; String? _error; void initState() { super.initState(); _presenter = UserPresenter(this, UserRepository()); _presenter.loadUser(1); } void showLoading() => setState(() => _isLoading = true); void hideLoading() => setState(() => _isLoading = false); void showUser(UserModel user) => setState(() => _user = user); void showError(String message) => setState(() => _error = message); Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('MVP Example')), body: _isLoading ? Center(child: CircularProgressIndicator()) : _error != null ? Center(child: Text(_error!)) : Center(child: Text('Hello, ${_user?.name}')), ); } }
Pros and Cons
| Pros | Cons |
|---|---|
| ✅ Highly testable — Presenter has no UI dependency | ❌ More boilerplate (interfaces needed) |
| ✅ View is completely passive (easy to swap) | ❌ One-to-one View ↔ Presenter coupling |
| ✅ Clear separation of concerns | ❌ Can still become complex in large apps |
| ✅ Good for unit testing Presenter | ❌ Harder to reuse Presenter across multiple Views |
Testing the Presenter (Unit Test)
dartclass MockUserView implements IUserView { bool loadingShown = false; UserModel? displayedUser; void showLoading() => loadingShown = true; void hideLoading() => loadingShown = false; void showUser(UserModel user) => displayedUser = user; void showError(String message) {} } void main() { test('UserPresenter loads user correctly', () async { final mockView = MockUserView(); final presenter = UserPresenter(mockView, UserRepository()); await presenter.loadUser(1); expect(mockView.displayedUser?.name, 'Alice'); expect(mockView.loadingShown, false); // hideLoading was called }); }
MVC vs MVP vs MVVM
| Feature | MVC | MVP | MVVM |
|---|---|---|---|
| View dependency | Controller knows View | Presenter uses interface | ViewModel has no View reference |
| Testability | Moderate | High | High |
| Data binding | Manual | Manual | Auto (reactive) |
| Boilerplate | Low | Medium | Medium-High |
| Flutter fit | ⚠️ Okay | ✅ Good | ✅✅ Best |
In Flutter, MVP is a solid, testable pattern. However, MVVM with
,textChangeNotifier, ortextRiverpodtends to be more idiomatic because Flutter's reactive model is built for data binding.textBLoC