Sembast

Sembast stands for "Simple Embedded Application Store" and is a lightweight yet powerful NoSQL database library for Dart and Flutter. Designed for single-process applications as a persistent NoSQL store, the entire document-based database resides in a single file and is loaded into memory when opened. Being 100% Dart implementation with no plugins required, it works on all platforms (Android/iOS/macOS/Linux/Windows/Web) and provides encryption support, high-performance read/write operations, and an intuitive API, making it an ideal modern database solution for mobile and desktop app development.

NoSQLFlutterDartEmbedded DatabaseEncryptionCross Platform

GitHub Overview

tekartik/sembast.dart

Simple io database

Stars836
Watchers14
Forks67
Created:December 6, 2014
Language:Dart
License:BSD 2-Clause "Simplified" License

Topics

None

Star History

tekartik/sembast.dart Star History
Data as of: 7/17/2025, 06:57 AM

Library

Sembast

Overview

Sembast stands for "Simple Embedded Application Store" and is a lightweight yet powerful NoSQL database library for Dart and Flutter. Designed for single-process applications as a persistent NoSQL store, the entire document-based database resides in a single file and is loaded into memory when opened. Being 100% Dart implementation with no plugins required, it works on all platforms (Android/iOS/macOS/Linux/Windows/Web) and provides encryption support, high-performance read/write operations, and an intuitive API, making it an ideal modern database solution for mobile and desktop app development.

Details

Sembast 2025 edition has established itself as one of the most trusted local database solutions in the Flutter ecosystem. As a JSON-like document-oriented database, it efficiently handles denormalized data and provides complex querying, indexing, and transaction capabilities. Integration with BLoC patterns makes collaboration with state management easy. Despite its simple file-based structure, it supports ACID-compliant transactions, reactive APIs, and large data processing, covering everything from small to medium-scale apps to full-scale production applications.

Key Features

  • Complete Cross-Platform: Works on all Flutter-supported platforms
  • Encryption Support: Strong encryption through user-defined codecs
  • High Performance: Memory-based processing with automatic file optimization
  • Simple API: Intuitive interface with low learning curve
  • Denormalized Data Support: Flexible JSON-based data structures
  • Reactive Programming: Stream-based real-time updates

Pros and Cons

Pros

  • No plugins required with 100% Dart code, cross-platform support improves development efficiency
  • Extremely easy setup with no boilerplate code required
  • Lightweight design perfect for small to medium-scale apps, fast read/write performance
  • Strong encryption features enable secure storage of sensitive data
  • File-based simple structure makes debugging and data verification easy
  • Excellent integration with state management libraries like BLoC

Cons

  • Single-process design makes it unsuitable for large-scale multi-user applications
  • Complex relational queries are difficult compared to SQL-based RDBMS
  • Memory-based processing has constraints for ultra-large data volumes
  • Transaction functionality is provided but not as robust as distributed databases
  • Limited interoperability with other database systems
  • Advanced enterprise-level features (replication, etc.) are not supported

Reference Pages

Code Examples

Setup and Installation

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  sembast: ^3.4.9
  path_provider: ^2.1.1  # For app directory access
  path: ^1.8.3

dev_dependencies:
  flutter_test:
    sdk: flutter
// Package imports
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';

Basic Database Operations

import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';

class DatabaseService {
  static Database? _database;
  static final _userStore = intMapStoreFactory.store('users');
  static final _postStore = intMapStoreFactory.store('posts');

  // Get database instance
  static Future<Database> get database async {
    if (_database != null) return _database!;
    
    // Get app directory
    final appDocumentDir = await getApplicationDocumentsDirectory();
    final dbPath = join(appDocumentDir.path, 'my_app.db');
    
    // Open database
    _database = await databaseFactoryIo.openDatabase(dbPath);
    return _database!;
  }

  // Close database
  static Future<void> close() async {
    if (_database != null) {
      await _database!.close();
      _database = null;
    }
  }

  // Add record
  static Future<int> addUser(Map<String, dynamic> userData) async {
    final db = await database;
    return await _userStore.add(db, userData);
  }

  // Get all users
  static Future<List<Map<String, dynamic>>> getAllUsers() async {
    final db = await database;
    final records = await _userStore.find(db);
    return records.map((record) => {
      'id': record.key,
      ...record.value,
    }).toList();
  }

  // Search user by ID
  static Future<Map<String, dynamic>?> getUserById(int id) async {
    final db = await database;
    final record = await _userStore.record(id).get(db);
    if (record != null) {
      return {'id': id, ...record};
    }
    return null;
  }

  // Update user
  static Future<void> updateUser(int id, Map<String, dynamic> userData) async {
    final db = await database;
    await _userStore.record(id).update(db, userData);
  }

  // Delete user
  static Future<void> deleteUser(int id) async {
    final db = await database;
    await _userStore.record(id).delete(db);
  }
}

// Usage example
void main() async {
  // Add user
  final userId = await DatabaseService.addUser({
    'name': 'John Doe',
    'email': '[email protected]',
    'age': 30,
    'createdAt': DateTime.now().toIso8601String(),
  });
  print('Added user ID: $userId');

  // Get all users
  final users = await DatabaseService.getAllUsers();
  print('All users: $users');

  // Update user
  await DatabaseService.updateUser(userId, {
    'name': 'John Doe (Updated)',
    'age': 31,
    'updatedAt': DateTime.now().toIso8601String(),
  });

  // Close database
  await DatabaseService.close();
}

Filtering and Query Operations

import 'package:sembast/sembast.dart';

class AdvancedQueryService {
  static final _userStore = intMapStoreFactory.store('users');
  static final _productStore = intMapStoreFactory.store('products');

  // Conditional search
  static Future<List<Map<String, dynamic>>> getUsersByAge(int minAge, int maxAge) async {
    final db = await DatabaseService.database;
    
    // Range filter by age
    final finder = Finder(
      filter: Filter.and([
        Filter.greaterThanOrEquals('age', minAge),
        Filter.lessThanOrEquals('age', maxAge),
      ]),
      sortOrders: [SortOrder('age')],
    );
    
    final records = await _userStore.find(db, finder: finder);
    return records.map((record) => {
      'id': record.key,
      ...record.value,
    }).toList();
  }

  // Complex filtering
  static Future<List<Map<String, dynamic>>> searchUsers({
    String? namePattern,
    String? emailDomain,
    bool? isActive,
    int? limit,
    int? offset,
  }) async {
    final db = await DatabaseService.database;
    
    List<Filter> filters = [];
    
    // Name partial match
    if (namePattern != null) {
      filters.add(Filter.matches('name', namePattern, anyInList: true));
    }
    
    // Email domain filter
    if (emailDomain != null) {
      filters.add(Filter.matches('email', '.*@$emailDomain'));
    }
    
    // Active user filter
    if (isActive != null) {
      filters.add(Filter.equals('isActive', isActive));
    }
    
    final finder = Finder(
      filter: filters.isNotEmpty ? Filter.and(filters) : null,
      sortOrders: [SortOrder('createdAt', false)], // Newest first
      limit: limit,
      offset: offset,
    );
    
    final records = await _userStore.find(db, finder: finder);
    return records.map((record) => {
      'id': record.key,
      ...record.value,
    }).toList();
  }

  // Custom queries
  static Future<Map<String, dynamic>> getUserStats() async {
    final db = await DatabaseService.database;
    
    // Total user count
    final totalUsers = await _userStore.count(db);
    
    // Active user count
    final activeUsers = await _userStore.count(
      db,
      filter: Filter.equals('isActive', true),
    );
    
    // Age group statistics
    final youngUsers = await _userStore.count(
      db,
      filter: Filter.lessThan('age', 30),
    );
    
    final middleAgedUsers = await _userStore.count(
      db,
      filter: Filter.and([
        Filter.greaterThanOrEquals('age', 30),
        Filter.lessThan('age', 50),
      ]),
    );
    
    final seniorUsers = await _userStore.count(
      db,
      filter: Filter.greaterThanOrEquals('age', 50),
    );
    
    return {
      'totalUsers': totalUsers,
      'activeUsers': activeUsers,
      'inactiveUsers': totalUsers - activeUsers,
      'ageDistribution': {
        'young': youngUsers,        // Under 30
        'middleAged': middleAgedUsers, // 30-49
        'senior': seniorUsers,      // 50+
      },
    };
  }

  // Pagination
  static Future<Map<String, dynamic>> getUsersPage({
    int page = 1,
    int itemsPerPage = 20,
    String? sortBy = 'createdAt',
    bool ascending = false,
  }) async {
    final db = await DatabaseService.database;
    
    final offset = (page - 1) * itemsPerPage;
    
    final finder = Finder(
      sortOrders: [SortOrder(sortBy!, ascending)],
      limit: itemsPerPage,
      offset: offset,
    );
    
    final records = await _userStore.find(db, finder: finder);
    final totalCount = await _userStore.count(db);
    
    return {
      'data': records.map((record) => {
        'id': record.key,
        ...record.value,
      }).toList(),
      'pagination': {
        'currentPage': page,
        'itemsPerPage': itemsPerPage,
        'totalItems': totalCount,
        'totalPages': (totalCount / itemsPerPage).ceil(),
        'hasNextPage': offset + itemsPerPage < totalCount,
        'hasPreviousPage': page > 1,
      },
    };
  }
}

Transactions and Batch Operations

import 'package:sembast/sembast.dart';

class TransactionService {
  static final _userStore = intMapStoreFactory.store('users');
  static final _orderStore = intMapStoreFactory.store('orders');
  static final _productStore = intMapStoreFactory.store('products');

  // Basic transaction
  static Future<void> transferUserData(
    int fromUserId,
    int toUserId,
    Map<String, dynamic> transferData,
  ) async {
    final db = await DatabaseService.database;
    
    await db.transaction((txn) async {
      // Read data from fromUser
      final fromUser = await _userStore.record(fromUserId).get(txn);
      if (fromUser == null) {
        throw Exception('Source user not found');
      }
      
      // Read data from toUser
      final toUser = await _userStore.record(toUserId).get(txn);
      if (toUser == null) {
        throw Exception('Target user not found');
      }
      
      // Remove/update data from fromUser
      final updatedFromUser = Map<String, dynamic>.from(fromUser);
      transferData.forEach((key, value) {
        if (updatedFromUser.containsKey(key)) {
          updatedFromUser.remove(key);
        }
      });
      
      // Add/update data to toUser
      final updatedToUser = Map<String, dynamic>.from(toUser);
      updatedToUser.addAll(transferData);
      updatedToUser['updatedAt'] = DateTime.now().toIso8601String();
      
      // Update both users
      await _userStore.record(fromUserId).put(txn, updatedFromUser);
      await _userStore.record(toUserId).put(txn, updatedToUser);
      
      print('Data transfer completed successfully');
    });
  }

  // Order processing transaction
  static Future<int> createOrderWithItems(
    int userId,
    List<Map<String, dynamic>> orderItems,
  ) async {
    final db = await DatabaseService.database;
    
    return await db.transaction<int>((txn) async {
      // Check user existence
      final user = await _userStore.record(userId).get(txn);
      if (user == null) {
        throw Exception('User not found');
      }
      
      // Check product inventory
      for (final item in orderItems) {
        final productId = item['productId'] as int;
        final quantity = item['quantity'] as int;
        
        final product = await _productStore.record(productId).get(txn);
        if (product == null) {
          throw Exception('Product ID $productId not found');
        }
        
        final currentStock = product['stock'] as int;
        if (currentStock < quantity) {
          throw Exception('Insufficient stock for product ID $productId');
        }
      }
      
      // Create order
      final orderId = await _orderStore.add(txn, {
        'userId': userId,
        'items': orderItems,
        'totalAmount': _calculateTotal(orderItems),
        'status': 'pending',
        'createdAt': DateTime.now().toIso8601String(),
      });
      
      // Reduce inventory
      for (final item in orderItems) {
        final productId = item['productId'] as int;
        final quantity = item['quantity'] as int;
        
        final product = await _productStore.record(productId).get(txn);
        final updatedProduct = Map<String, dynamic>.from(product!);
        updatedProduct['stock'] = (updatedProduct['stock'] as int) - quantity;
        updatedProduct['updatedAt'] = DateTime.now().toIso8601String();
        
        await _productStore.record(productId).put(txn, updatedProduct);
      }
      
      print('Order $orderId created successfully');
      return orderId;
    });
  }

  // Batch operations
  static Future<void> batchUpdateUsers(
    List<Map<String, dynamic>> updates,
  ) async {
    final db = await DatabaseService.database;
    
    await db.transaction((txn) async {
      for (final update in updates) {
        final userId = update['id'] as int;
        final userData = Map<String, dynamic>.from(update);
        userData.remove('id'); // Remove ID
        userData['updatedAt'] = DateTime.now().toIso8601String();
        
        await _userStore.record(userId).update(txn, userData);
      }
      
      print('${updates.length} users updated in batch');
    });
  }

  // Data cleanup
  static Future<void> cleanupOldData(Duration maxAge) async {
    final db = await DatabaseService.database;
    
    await db.transaction((txn) async {
      final cutoffDate = DateTime.now().subtract(maxAge);
      final cutoffString = cutoffDate.toIso8601String();
      
      // Delete old users
      final oldUserFinder = Finder(
        filter: Filter.lessThan('createdAt', cutoffString),
      );
      
      final deletedUserCount = await _userStore.delete(txn, finder: oldUserFinder);
      
      // Delete old orders
      final oldOrderFinder = Finder(
        filter: Filter.and([
          Filter.lessThan('createdAt', cutoffString),
          Filter.equals('status', 'completed'),
        ]),
      );
      
      final deletedOrderCount = await _orderStore.delete(txn, finder: oldOrderFinder);
      
      print('Cleanup completed: deleted $deletedUserCount users, $deletedOrderCount orders');
    });
  }

  static double _calculateTotal(List<Map<String, dynamic>> items) {
    return items.fold(0.0, (total, item) {
      final price = item['price'] as double;
      final quantity = item['quantity'] as int;
      return total + (price * quantity);
    });
  }
}

Encryption and Security

import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'dart:typed_data';

// Custom encryption codec
class EncryptionCodec extends SembastCodec {
  final String _password;
  
  EncryptionCodec(this._password);

  @override
  String get signature => 'encrypt_v1';

  @override
  Uint8List encode(Object? input) {
    if (input == null) return Uint8List(0);
    
    // JSON encode data
    final jsonString = jsonEncode(input);
    final bytes = utf8.encode(jsonString);
    
    // Simple encryption (use proper encryption library in production)
    final key = sha256.convert(utf8.encode(_password)).bytes;
    final encrypted = _xorEncrypt(bytes, key);
    
    return Uint8List.fromList(encrypted);
  }

  @override
  Object? decode(Uint8List encoded) {
    if (encoded.isEmpty) return null;
    
    // Decrypt
    final key = sha256.convert(utf8.encode(_password)).bytes;
    final decrypted = _xorEncrypt(encoded, key);
    
    // JSON decode
    final jsonString = utf8.decode(decrypted);
    return jsonDecode(jsonString);
  }

  List<int> _xorEncrypt(List<int> data, List<int> key) {
    final result = <int>[];
    for (int i = 0; i < data.length; i++) {
      result.add(data[i] ^ key[i % key.length]);
    }
    return result;
  }
}

class SecureDatabaseService {
  static Database? _database;
  static final _userStore = intMapStoreFactory.store('users');
  static final _sensitiveStore = intMapStoreFactory.store('sensitive_data');

  // Initialize encrypted database
  static Future<Database> initializeSecureDatabase(String password) async {
    if (_database != null) return _database!;
    
    final appDocumentDir = await getApplicationDocumentsDirectory();
    final dbPath = join(appDocumentDir.path, 'secure_app.db');
    
    // Create encryption codec
    final codec = EncryptionCodec(password);
    
    // Open encrypted database
    _database = await databaseFactoryIo.openDatabase(
      dbPath,
      codec: codec,
    );
    
    return _database!;
  }

  // Store sensitive data
  static Future<int> storeSensitiveData(Map<String, dynamic> data) async {
    final db = await _database!;
    
    // Save data with timestamp
    final secureData = {
      ...data,
      'encryptedAt': DateTime.now().toIso8601String(),
      'hash': _generateDataHash(data),
    };
    
    return await _sensitiveStore.add(db, secureData);
  }

  // Retrieve and verify sensitive data
  static Future<Map<String, dynamic>?> getSensitiveData(int id) async {
    final db = await _database!;
    final record = await _sensitiveStore.record(id).get(db);
    
    if (record == null) return null;
    
    // Verify data integrity
    final storedHash = record['hash'] as String;
    final dataWithoutHash = Map<String, dynamic>.from(record);
    dataWithoutHash.remove('hash');
    dataWithoutHash.remove('encryptedAt');
    
    final calculatedHash = _generateDataHash(dataWithoutHash);
    
    if (storedHash != calculatedHash) {
      throw Exception('Data may have been tampered with');
    }
    
    return {
      'id': id,
      ...record,
    };
  }

  static String _generateDataHash(Map<String, dynamic> data) {
    final jsonString = jsonEncode(data);
    final bytes = utf8.encode(jsonString);
    final digest = sha256.convert(bytes);
    return digest.toString();
  }

  // Create secure backup
  static Future<void> createSecureBackup(String backupPassword) async {
    final db = await _database!;
    final appDocumentDir = await getApplicationDocumentsDirectory();
    final backupPath = join(appDocumentDir.path, 'backup_${DateTime.now().millisecondsSinceEpoch}.db');
    
    // New codec for backup
    final backupCodec = EncryptionCodec(backupPassword);
    
    // Create backup database
    final backupDb = await databaseFactoryIo.openDatabase(
      backupPath,
      codec: backupCodec,
    );
    
    try {
      // Export all data
      final users = await _userStore.find(db);
      final sensitiveData = await _sensitiveStore.find(db);
      
      final backupUserStore = intMapStoreFactory.store('users');
      final backupSensitiveStore = intMapStoreFactory.store('sensitive_data');
      
      // Restore data to backup database
      await backupDb.transaction((txn) async {
        for (final record in users) {
          await backupUserStore.record(record.key).put(txn, record.value);
        }
        
        for (final record in sensitiveData) {
          await backupSensitiveStore.record(record.key).put(txn, record.value);
        }
      });
      
      print('Secure backup created: $backupPath');
    } finally {
      await backupDb.close();
    }
  }
}

// Usage example
void main() async {
  try {
    // Initialize secure database
    await SecureDatabaseService.initializeSecureDatabase('my_secret_password_123');
    
    // Store sensitive data
    final sensitiveId = await SecureDatabaseService.storeSensitiveData({
      'creditCardNumber': '1234-5678-9012-3456',
      'expiryDate': '12/25',
      'holderName': 'John Doe',
      'notes': 'Confidential information',
    });
    
    // Retrieve sensitive data
    final retrievedData = await SecureDatabaseService.getSensitiveData(sensitiveId);
    print('Retrieved data: $retrievedData');
    
    // Create secure backup
    await SecureDatabaseService.createSecureBackup('backup_password_456');
    
  } catch (e) {
    print('Error: $e');
  }
}

Reactive Programming and Streams

import 'package:sembast/sembast.dart';
import 'dart:async';

class ReactiveDataService {
  static final _userStore = intMapStoreFactory.store('users');
  static final _chatStore = intMapStoreFactory.store('chat_messages');
  
  // Watch user changes
  static Stream<List<Map<String, dynamic>>> watchUsers() async* {
    final db = await DatabaseService.database;
    
    await for (final snapshot in _userStore.query().onSnapshots(db)) {
      final users = snapshot.map((record) => {
        'id': record.key,
        ...record.value,
      }).toList();
      
      yield users;
    }
  }

  // Watch specific condition users
  static Stream<List<Map<String, dynamic>>> watchActiveUsers() async* {
    final db = await DatabaseService.database;
    
    final query = _userStore.query(
      finder: Finder(
        filter: Filter.equals('isActive', true),
        sortOrders: [SortOrder('lastLoginAt', false)],
      ),
    );
    
    await for (final snapshot in query.onSnapshots(db)) {
      final activeUsers = snapshot.map((record) => {
        'id': record.key,
        ...record.value,
      }).toList();
      
      yield activeUsers;
    }
  }

  // Watch chat messages in real-time
  static Stream<List<Map<String, dynamic>>> watchChatMessages(int chatRoomId) async* {
    final db = await DatabaseService.database;
    
    final query = _chatStore.query(
      finder: Finder(
        filter: Filter.equals('chatRoomId', chatRoomId),
        sortOrders: [SortOrder('timestamp')],
      ),
    );
    
    await for (final snapshot in query.onSnapshots(db)) {
      final messages = snapshot.map((record) => {
        'id': record.key,
        ...record.value,
      }).toList();
      
      yield messages;
    }
  }

  // Watch count changes
  static Stream<int> watchUserCount() async* {
    final db = await DatabaseService.database;
    
    await for (final _ in _userStore.query().onSnapshots(db)) {
      final count = await _userStore.count(db);
      yield count;
    }
  }

  // Watch complex conditions
  static Stream<Map<String, dynamic>> watchUserStatistics() async* {
    final db = await DatabaseService.database;
    
    await for (final _ in _userStore.query().onSnapshots(db)) {
      final totalCount = await _userStore.count(db);
      final activeCount = await _userStore.count(
        db,
        filter: Filter.equals('isActive', true),
      );
      final newUsersToday = await _userStore.count(
        db,
        filter: Filter.greaterThan(
          'createdAt',
          DateTime.now().subtract(Duration(days: 1)).toIso8601String(),
        ),
      );
      
      yield {
        'totalUsers': totalCount,
        'activeUsers': activeCount,
        'inactiveUsers': totalCount - activeCount,
        'newUsersToday': newUsersToday,
        'lastUpdated': DateTime.now().toIso8601String(),
      };
    }
  }

  // Send message with real-time updates
  static Future<void> sendChatMessage(
    int chatRoomId,
    int senderId,
    String message,
  ) async {
    final db = await DatabaseService.database;
    
    await _chatStore.add(db, {
      'chatRoomId': chatRoomId,
      'senderId': senderId,
      'message': message,
      'timestamp': DateTime.now().toIso8601String(),
      'isRead': false,
    });
    
    // This addition automatically notifies all listeners
    // watching watchChatMessages() of the new message
  }

  // Batch operations with real-time updates
  static Future<void> markMessagesAsRead(
    int chatRoomId,
    int userId,
  ) async {
    final db = await DatabaseService.database;
    
    await db.transaction((txn) async {
      final finder = Finder(
        filter: Filter.and([
          Filter.equals('chatRoomId', chatRoomId),
          Filter.notEquals('senderId', userId),
          Filter.equals('isRead', false),
        ]),
      );
      
      await _chatStore.update(
        txn,
        {'isRead': true, 'readAt': DateTime.now().toIso8601String()},
        finder: finder,
      );
    });
    
    // This update automatically triggers real-time monitoring
  }
}

// Flutter Widget usage example
class UserListWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Map<String, dynamic>>>(
      stream: ReactiveDataService.watchUsers(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }
        
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        
        final users = snapshot.data ?? [];
        
        return ListView.builder(
          itemCount: users.length,
          itemBuilder: (context, index) {
            final user = users[index];
            return ListTile(
              title: Text(user['name'] ?? ''),
              subtitle: Text(user['email'] ?? ''),
              trailing: user['isActive'] == true
                ? Icon(Icons.circle, color: Colors.green, size: 12)
                : Icon(Icons.circle, color: Colors.grey, size: 12),
            );
          },
        );
      },
    );
  }
}

// Chat UI example
class ChatWidget extends StatelessWidget {
  final int chatRoomId;
  
  const ChatWidget({Key? key, required this.chatRoomId}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Map<String, dynamic>>>(
      stream: ReactiveDataService.watchChatMessages(chatRoomId),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Center(child: CircularProgressIndicator());
        }
        
        final messages = snapshot.data!;
        
        return ListView.builder(
          itemCount: messages.length,
          itemBuilder: (context, index) {
            final message = messages[index];
            return ListTile(
              title: Text(message['message'] ?? ''),
              subtitle: Text(message['timestamp'] ?? ''),
              leading: CircleAvatar(
                child: Text('${message['senderId']}'),
              ),
              trailing: message['isRead'] == true
                ? Icon(Icons.done_all, color: Colors.blue)
                : Icon(Icons.done, color: Colors.grey),
            );
          },
        );
      },
    );
  }
}