Question #144MediumOOP Concepts

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 DAOWith DAO
DB queries scattered across the codebaseAll DB queries in one place
Hard to swap databaseSwap DB by changing only the DAO
Hard to unit test (real DB needed)Easy to mock DAO for testing
Business logic mixed with SQLClean 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

dart
class 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)

dart
abstract 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)

dart
import '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)

dart
class 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

AspectDAORepository
FocusSingle data source (DB)Multiple data sources (API + DB)
Abstraction levelLow-level (CRUD operations)Higher-level (business queries)
CachingUsually not handledCan combine local + remote
Use caseDirect DB accessApp-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.