What is DAO(Data Access Object) and its usage?
Answer
Overview
DAO (Data Access Object) is a design pattern that provides an abstract interface for accessing data from a database or any other data source. It separates the data access logic from the business logic, making your code cleaner and more testable.
Think of DAO as a "middleman" between your app logic and the database. Your app never talks to the DB directly — it always goes through the DAO.
Why Use DAO?
| Without DAO | With DAO |
|---|---|
| DB queries scattered across the codebase | All DB queries in one place |
| Hard to swap database | Swap DB by changing only the DAO |
| Hard to unit test (real DB needed) | Easy to mock DAO for testing |
| Business logic mixed with SQL | Clean separation of concerns |
DAO Structure
text┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Business │─────▶│ DAO │─────▶│ Database │ │ Logic │ │ (Interface) │ │ (SQLite / │ │ (Service) │ │ │ │ Firestore) │ └──────────────┘ └──────────────┘ └──────────────┘
DAO Example in Flutter (with SQLite / sqflite)
1. Define the Model
dartclass User { final int? id; final String name; final String email; User({this.id, required this.name, required this.email}); Map<String, dynamic> toMap() => { 'id': id, 'name': name, 'email': email, }; factory User.fromMap(Map<String, dynamic> map) => User( id: map['id'], name: map['name'], email: map['email'], ); }
2. Define the DAO Interface (Abstract)
dartabstract class IUserDao { Future<int> insertUser(User user); Future<User?> getUserById(int id); Future<List<User>> getAllUsers(); Future<int> updateUser(User user); Future<int> deleteUser(int id); }
3. Implement the DAO (SQLite)
dartimport 'package:sqflite/sqflite.dart'; class UserDao implements IUserDao { final Database _db; UserDao(this._db); Future<int> insertUser(User user) async { return await _db.insert( 'users', user.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } Future<User?> getUserById(int id) async { final maps = await _db.query( 'users', where: 'id = ?', whereArgs: [id], ); if (maps.isEmpty) return null; return User.fromMap(maps.first); } Future<List<User>> getAllUsers() async { final maps = await _db.query('users'); return maps.map((m) => User.fromMap(m)).toList(); } Future<int> updateUser(User user) async { return await _db.update( 'users', user.toMap(), where: 'id = ?', whereArgs: [user.id], ); } Future<int> deleteUser(int id) async { return await _db.delete( 'users', where: 'id = ?', whereArgs: [id], ); } }
4. Usage in Business Logic (Service)
dartclass UserService { final IUserDao _userDao; UserService(this._userDao); Future<void> registerUser(String name, String email) async { // Validation (business logic) if (name.isEmpty || !email.contains('@')) { throw Exception('Invalid input'); } // Delegate DB operation to DAO final user = User(name: name, email: email); await _userDao.insertUser(user); } Future<List<User>> getActiveUsers() async { final users = await _userDao.getAllUsers(); return users.where((u) => u.email.isNotEmpty).toList(); } }
DAO with Drift (Type-Safe ORM for Flutter)
Drift (formerly moor) provides built-in DAO support:
dart(tables: [Users]) class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin { UserDao(AppDatabase db) : super(db); Future<List<User>> getAllUsers() => select(users).get(); Stream<List<User>> watchAllUsers() => select(users).watch(); Future<int> insertUser(UsersCompanion user) => into(users).insert(user); }
DAO vs Repository Pattern
| Aspect | DAO | Repository |
|---|---|---|
| Focus | Single data source (DB) | Multiple data sources (API + DB) |
| Abstraction level | Low-level (CRUD operations) | Higher-level (business queries) |
| Caching | Usually not handled | Can combine local + remote |
| Use case | Direct DB access | App-level data management |
Best Practice: Use DAO for raw database CRUD operations, and wrap it inside a Repository that can also call APIs and handle caching. This gives a clean two-layer data access architecture.