Flutter Architecture: Models, Providers, DAO, and Repositories

This document explains how models, providers, DAOs, and repositories work together in a Flutter app, and shows how data flows between layers.


1. Models

  • Definition: Plain Dart classes that represent your app’s data.
  • Purpose: Define the shape of data, often including JSON serialization methods.
class User {
  final int id;
  final String name;
  final String email;

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

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

  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
  };
}

2. DAO (Data Access Object)

  • Definition: Encapsulates raw database queries.
  • Purpose: Handles persistence logic, usually with SQLite/Drift.
@DriftAccessor(tables: [Users])
class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
  UserDao(AppDatabase db) : super(db);

  Future<List<User>> getAllUsers() => select(users).get();
  Future<int> insertUser(User user) => into(users).insert(user);
}

3. Repository

  • Definition: A single source of truth for data, sitting between data sources and the rest of the app.
  • Purpose: Chooses between API and DAO, applies caching, and provides a unified interface.
class UserRepository {
  final UserDao userDao;
  final ApiService apiService;

  UserRepository({required this.userDao, required this.apiService});

  Future<List<User>> getUsers() async {
    try {
      final users = await apiService.fetchUsers();
      await userDao.insertUsers(users);
      return users;
    } catch (_) {
      return userDao.getAllUsers();
    }
  }
}

4. Provider

  • Definition: A state management tool that exposes repositories to the UI.
  • Purpose: Notifies widgets of changes and holds UI state.
class UserProvider with ChangeNotifier {
  final UserRepository repository;
  List<User> _users = [];
  bool _loading = false;

  List<User> get users => _users;
  bool get loading => _loading;

  UserProvider(this.repository);

  Future<void> loadUsers() async {
    _loading = true;
    notifyListeners();

    _users = await repository.getUsers();

    _loading = false;
    notifyListeners();
  }
}

5. How They Work Together

+-------------------------+
|         UI              |
|  (Widgets/Screens)      |
+-----------+-------------+
            |
            | reads state / calls actions
            v
+-------------------------+
|       Provider          |
| (ChangeNotifier, etc.)  |
+-----------+-------------+
            | uses
            v
+-------------------------+
|       Repository        |
|  (single source of truth|
|   + orchestration)      |
+-----+---------------+---+
      |               |
      | chooses source|
      v               v
+-----------+     +---------+
|   DAO     |     |  API    |
| (Local DB |     | Client  |
|  / cache) |     | (HTTP)  |
+-----+-----+     +----+----+
      |                |
      | maps to/from   | maps to/from
      v                v
   +-----------------------+
   |        Models         |
   | (User, Post, etc.)    |
   +-----------------------+

Request Flow

UI → Provider → Repository → (API or DAO) → Models → back to UI

Key Takeaways

  • Models define the structure of data.
  • DAO handles persistence logic.
  • Repository abstracts data sources.
  • Provider exposes data/state to the UI.
  • UI remains reactive and unaware of data source details.

Visit Emlekezik.com