Skip to content
Blog

Flutter Clean Architecture: The Domain Layer Deep Dive

Explore the Domain layer in Flutter Clean Architecture — entities, use cases, and repository interfaces that keep your business logic pure and testable.

Published on • September 17, 2026

AI Assistant

The Heart of Your App

The Domain layer is the innermost ring of Clean Architecture. It contains your core business logic — entities and use cases — and has zero dependencies on Flutter, Dart packages, or any external framework.

This is where your app’s rules live, independent of UI, databases, or APIs.

Entities: Business Objects

Entities are plain Dart classes representing your core domain concepts. They should be immutable and contain no logic beyond validation.

class User {
  final String id;
  final String email;
  final String displayName;
  final DateTime createdAt;

  const User({
    required this.id,
    required this.email,
    required this.displayName,
    required this.createdAt,
  });
}

class Transaction {
  final String id;
  final String userId;
  final double amount;
  final TransactionType type;
  final DateTime timestamp;

  const Transaction({
    required this.id,
    required this.userId,
    required this.amount,
    required this.type,
    required this.timestamp,
  });
}

Use Cases: Single-Responsibility Actions

Each use case performs one business action. They receive input, call repository interfaces, and return output.

class GetTransactionsForUser {
  final TransactionRepository _repo;

  const GetTransactionsForUser(this._repo);

  Future<List<Transaction>> call(String userId, {DateTime? since}) async {
    return _repo.getTransactions(userId: userId, since: since);
  }
}

Use cases implement the call method for clean syntax:

final transactions = await getTransactionsForUser(userId, since: lastWeek);

Repository Interfaces

Domain defines repository contracts — abstract classes that Data implements:

abstract class TransactionRepository {
  Future<List<Transaction>> getTransactions({
    required String userId,
    DateTime? since,
  });
  Future<Transaction> createTransaction(Transaction transaction);
}

This inversion of dependency is the key principle of Clean Architecture: Domain never depends on Data; Data depends on Domain.

Testing Domain

Domain logic is trivial to test:

test('GetTransactionsForUser returns filtered list', () async {
  final repo = MockTransactionRepository();
  when(() => repo.getTransactions(userId: '1')).thenAnswer(
    (_) async => [Transaction(id: '1', userId: '1', amount: 50, type: TransactionType.credit, timestamp: DateTime.now())],
  );

  final useCase = GetTransactionsForUser(repo);
  final result = await useCase('1');

  expect(result.length, 1);
});

No widgets. No platform channels. Pure Dart tests in milliseconds.

Conclusion

The Domain layer is your app’s immune system — it protects business rules from external chaos. Keep it pure, keep it simple, and your Flutter app will be maintainable for years.