IntermediateArchitecture

What is the Repository pattern?

Short Answer

The Repository pattern puts a single abstraction in front of however data is actually fetched or stored — network, local cache, database — so the rest of the app talks to one consistent interface and doesn't care where the data comes from.

Instead of widgets or use cases calling an HTTP client directly, they depend on a repository interface like `abstract class UserRepository { Future<User> getUser(String id); }`. The concrete implementation decides internally whether to hit the network, read a local cache, or merge both (e.g. return cached data immediately, then refresh from network) — that decision is invisible to everything calling the repository.

This gives you two big wins: you can swap the underlying data source (REST today, GraphQL tomorrow, or add an offline cache) without touching any calling code, and you can substitute a fake/mock repository in tests so business logic tests never make real network calls.

Code Example

abstract class UserRepository {
  Future<User> getUser(String id);
}

class UserRepositoryImpl implements UserRepository {
  UserRepositoryImpl(this._api, this._cache);
  final ApiClient _api;
  final LocalCache _cache;

  @override
  Future<User> getUser(String id) async {
    final cached = _cache.getUser(id);
    if (cached != null) return cached;
    final user = await _api.fetchUser(id);
    _cache.saveUser(user);
    return user;
  }
}

Common Mistakes

  • ×Creating a repository that just forwards every call to one data source with no added logic — at that point it's not adding value over calling the data source directly.
  • ×Leaking data-source-specific types (like an HTTP response model) out through the repository interface instead of returning domain entities.