Flutter – Stop Passing BuildContext: A Cleaner Approach

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'),
    );
  }
}