Question #195EasyArchitectureImportant

What is separation of concerns ?

Answer

Overview

Separation of Concerns (SoC) is a software design principle that states: each part of your code should have one clear responsibility and should not overlap with other parts.

Think of a restaurant: the chef cooks, the waiter serves, the cashier handles payments. They are separate — each person knows their job.


Why It Matters

Without SoCWith SoC
UI code mixed with API callsUI only handles display
Business logic inside widgetsLogic lives in separate classes
Hard to test individual piecesEach layer is independently testable
Hard to maintain or changeEasy to swap out one layer

The Layers in Flutter

text
┌────────────────────────────────────┐
│           View / UI Layer          │  ← Widgets, Screens
├────────────────────────────────────┤
│     Presentation / State Layer     │  ← ViewModel, BLoC, Controller
├────────────────────────────────────┤
│        Business Logic Layer        │  ← Use Cases, Services
├────────────────────────────────────┤
│          Data / Repository Layer   │  ← Repository, API calls
├────────────────────────────────────┤
│           Model Layer              │  ← Data models, entities
└────────────────────────────────────┘

❌ Bad Example — No Separation of Concerns

dart
// Everything crammed into the widget — bad practice!
class UserScreen extends StatefulWidget {
  
  _UserScreenState createState() => _UserScreenState();
}

class _UserScreenState extends State<UserScreen> {
  String? userName;

  
  void initState() {
    super.initState();
    // ❌ API call directly inside widget
    http.get(Uri.parse('https://api.example.com/user/1')).then((response) {
      final data = jsonDecode(response.body);
      setState(() => userName = data['name']); // ❌ parsing inside widget
    });
  }

  
  Widget build(BuildContext context) {
    return Text(userName ?? 'Loading...');
  }
}

✅ Good Example — With Separation of Concerns

1. Model Layer

dart
class UserModel {
  final int id;
  final String name;
  final String email;

  UserModel({required this.id, required this.name, required this.email});

  factory UserModel.fromJson(Map<String, dynamic> json) {
    return UserModel(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

2. Repository Layer (Data)

dart
class UserRepository {
  final Dio _dio;

  UserRepository(this._dio);

  Future<UserModel> getUser(int id) async {
    final response = await _dio.get('/users/$id');
    return UserModel.fromJson(response.data);
  }
}

3. Business Logic / Use Case

dart
class GetUserUseCase {
  final UserRepository _repository;

  GetUserUseCase(this._repository);

  Future<UserModel> execute(int userId) {
    if (userId <= 0) throw Exception('Invalid user ID');
    return _repository.getUser(userId);
  }
}

4. State / ViewModel Layer

dart
class UserViewModel extends ChangeNotifier {
  final GetUserUseCase _useCase;

  UserModel? user;
  bool isLoading = false;
  String? error;

  UserViewModel(this._useCase);

  Future<void> loadUser(int id) async {
    isLoading = true;
    notifyListeners();

    try {
      user = await _useCase.execute(id);
    } catch (e) {
      error = e.toString();
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}

5. View Layer

dart
class UserScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final vm = context.watch<UserViewModel>();

    if (vm.isLoading) return CircularProgressIndicator();
    if (vm.error != null) return Text('Error: ${vm.error}');

    return Text('Hello, ${vm.user?.name}');
    // ✅ View only renders — no logic here
  }
}

Benefits of Separation of Concerns

  • Testability — Each layer can be unit tested in isolation
  • Maintainability — Change one layer without touching others
  • Reusability — Repository and use cases can be reused across screens
  • Readability — Each file has a single, clear purpose
  • Team scalability — Different devs can work on different layers

SoC in Flutter Architecture Patterns

PatternHow SoC is Applied
MVCModel, View, Controller separated
MVPModel, View (interface), Presenter separated
MVVMModel, ViewModel, View separated
BLoCEvents → BLoC → States → UI
Clean ArchitectureDomain, Data, Presentation layers

Key Rule: If a widget is doing more than rendering UI, you have a separation of concerns violation. Move logic to a ViewModel, Controller, or Repository.