Problem: Architectural Coupling
Testing: Mocking BuildContext in Unit Tests is complex and unnecessary.
Separation of Concerns: Business logic should not depend on the Widget tree.
Solution: Command Pattern Approach
The base class:
import 'package:flutter/material.dart';
/// Base class for all UI actions triggered by business logic.
abstract class UICommand {
void execute(BuildContext context);
}Example Implementation:
class ShowDialogCommand implements UICommand {
final String title;
final String content;
ShowDialogCommand({required this.title, required this.content});
@override
void execute(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
}Service Layer:
class UserService {
Future<UICommand> updateProfile(String username) async {
final success = await api.update(username);
if (success) {
return ShowDialogCommand(
title: 'Update Successful',
content: 'Your profile has been updated.',
);
} else {
return ShowDialogCommand(
title: 'Error',
content: 'Failed to update profile. Please try again.',
);
}
}
}UI Layer:
class UpdateButton extends StatelessWidget {
final TextEditingController _controller = TextEditingController();
UpdateButton({super.key});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
// 1. Logic Layer: Returns the intent
final service = locator<UserService>();
final command = await service.updateProfile(_controller.text);
// 2. UI Layer: Executes the side-effect safely
if (context.mounted) {
command.execute(context);
}
},
child: const Text('Update Profile'),
);
}
}