SQLite (sqflite)

sqflite is a lightweight and high-performance SQLite database library for Flutter, positioned as the "definitive solution for local data persistence on iOS, Android, and macOS." As the de facto standard SQLite implementation in the Flutter ecosystem, it provides a comprehensive solution for structured data management in mobile applications, balancing native performance and type safety, specifically tailored for mobile app development.

FlutterDartSQLiteMobileDatabaseLocal Storage

Library

SQLite (sqflite)

Overview

sqflite is a lightweight and high-performance SQLite database library for Flutter, positioned as the "definitive solution for local data persistence on iOS, Android, and macOS." As the de facto standard SQLite implementation in the Flutter ecosystem, it provides a comprehensive solution for structured data management in mobile applications, balancing native performance and type safety, specifically tailored for mobile app development.

Details

sqflite 2025 version fully integrates with Flutter 3.0+ latest features, delivering a consistent SQLite database experience across iOS, Android, macOS, and Web (experimental). Through direct integration with the native SQLite engine, it maintains high performance and memory efficiency while providing modern asynchronous programming patterns through Dart Future-based APIs and async/await support. It comprehensively supports database schema management, migrations, batch processing, and transaction control, suitable for enterprise-level mobile application development.

Key Features

  • Multi-platform Support: Unified API across iOS, Android, macOS, and Web (experimental)
  • High-performance Native Implementation: Optimized performance through direct SQLite engine integration
  • Asynchronous Processing Support: Modern API design based on Future/async-await
  • Comprehensive CRUD Features: Complete CRUD operations and SQL statement execution support
  • Schema Management: Database versioning and migration capabilities
  • Transaction Control: Safe data operations with guaranteed ACID properties

Pros and Cons

Pros

  • Standard position in Flutter ecosystem with abundant learning resources
  • High-speed database operations through native performance
  • Lightweight implementation with minimal impact on app size
  • Safe transaction processing with robust ACID properties
  • Low learning cost and high development efficiency through intuitive Dart API
  • Rich community support and third-party integrations

Cons

  • Limited advanced relational features due to SQLite constraints
  • Local storage only; cloud sync functionality requires separate implementation
  • Performance constraints with large datasets
  • Web implementation is experimental with existing limitations
  • Readability challenges with raw SQL writing for complex query construction
  • Unsuitable for NoSQL data models

Reference Pages

Code Examples

Project Setup and Dependencies

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.4.2
  path: ^1.8.3
  
dev_dependencies:
  flutter_test:
    sdk: flutter

# Dart imports
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

Database Initialization and Setup

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static const _databaseName = "app_database.db";
  static const _databaseVersion = 1;
  
  // Table definitions
  static const String tableUsers = 'users';
  static const String tablePosts = 'posts';
  
  // User table columns
  static const String columnUserId = 'id';
  static const String columnUserName = 'name';
  static const String columnUserEmail = 'email';
  static const String columnUserAge = 'age';
  static const String columnUserCreatedAt = 'created_at';
  
  // Post table columns
  static const String columnPostId = 'id';
  static const String columnPostTitle = 'title';
  static const String columnPostContent = 'content';
  static const String columnPostUserId = 'user_id';
  static const String columnPostCreatedAt = 'created_at';
  
  static Database? _database;
  
  // Singleton pattern for database instance management
  static Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }
  
  // Database initialization
  static Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), _databaseName);
    
    return await openDatabase(
      path,
      version: _databaseVersion,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }
  
  // Table creation
  static Future<void> _onCreate(Database db, int version) async {
    // Create users table
    await db.execute('''
      CREATE TABLE $tableUsers (
        $columnUserId INTEGER PRIMARY KEY AUTOINCREMENT,
        $columnUserName TEXT NOT NULL,
        $columnUserEmail TEXT UNIQUE NOT NULL,
        $columnUserAge INTEGER,
        $columnUserCreatedAt TEXT NOT NULL
      )
    ''');
    
    // Create posts table
    await db.execute('''
      CREATE TABLE $tablePosts (
        $columnPostId INTEGER PRIMARY KEY AUTOINCREMENT,
        $columnPostTitle TEXT NOT NULL,
        $columnPostContent TEXT NOT NULL,
        $columnPostUserId INTEGER NOT NULL,
        $columnPostCreatedAt TEXT NOT NULL,
        FOREIGN KEY ($columnPostUserId) REFERENCES $tableUsers ($columnUserId)
      )
    ''');
    
    // Create indexes
    await db.execute('''
      CREATE INDEX idx_posts_user_id ON $tablePosts($columnPostUserId)
    ''');
    
    await db.execute('''
      CREATE INDEX idx_users_email ON $tableUsers($columnUserEmail)
    ''');
  }
  
  // Database upgrade
  static Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 2) {
      // Version 2 migration example
      await db.execute('''
        ALTER TABLE $tableUsers ADD COLUMN profile_image TEXT
      ''');
    }
    
    if (oldVersion < 3) {
      // Version 3 migration example
      await db.execute('''
        CREATE TABLE categories (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL,
          description TEXT
        )
      ''');
    }
  }
  
  // Close database connection
  static Future<void> closeDatabase() async {
    final db = _database;
    if (db != null) {
      await db.close();
      _database = null;
    }
  }
}

Model Class Definition

// User model
class User {
  final int? id;
  final String name;
  final String email;
  final int? age;
  final DateTime createdAt;
  
  User({
    this.id,
    required this.name,
    required this.email,
    this.age,
    required this.createdAt,
  });
  
  // Map conversion from database
  factory User.fromMap(Map<String, dynamic> map) {
    return User(
      id: map[DatabaseHelper.columnUserId],
      name: map[DatabaseHelper.columnUserName],
      email: map[DatabaseHelper.columnUserEmail],
      age: map[DatabaseHelper.columnUserAge],
      createdAt: DateTime.parse(map[DatabaseHelper.columnUserCreatedAt]),
    );
  }
  
  // Map conversion to database
  Map<String, dynamic> toMap() {
    return {
      DatabaseHelper.columnUserId: id,
      DatabaseHelper.columnUserName: name,
      DatabaseHelper.columnUserEmail: email,
      DatabaseHelper.columnUserAge: age,
      DatabaseHelper.columnUserCreatedAt: createdAt.toIso8601String(),
    };
  }
  
  // Debug string representation
  @override
  String toString() {
    return 'User{id: $id, name: $name, email: $email, age: $age, createdAt: $createdAt}';
  }
  
  // Copy with method
  User copyWith({
    int? id,
    String? name,
    String? email,
    int? age,
    DateTime? createdAt,
  }) {
    return User(
      id: id ?? this.id,
      name: name ?? this.name,
      email: email ?? this.email,
      age: age ?? this.age,
      createdAt: createdAt ?? this.createdAt,
    );
  }
}

// Post model
class Post {
  final int? id;
  final String title;
  final String content;
  final int userId;
  final DateTime createdAt;
  
  Post({
    this.id,
    required this.title,
    required this.content,
    required this.userId,
    required this.createdAt,
  });
  
  factory Post.fromMap(Map<String, dynamic> map) {
    return Post(
      id: map[DatabaseHelper.columnPostId],
      title: map[DatabaseHelper.columnPostTitle],
      content: map[DatabaseHelper.columnPostContent],
      userId: map[DatabaseHelper.columnPostUserId],
      createdAt: DateTime.parse(map[DatabaseHelper.columnPostCreatedAt]),
    );
  }
  
  Map<String, dynamic> toMap() {
    return {
      DatabaseHelper.columnPostId: id,
      DatabaseHelper.columnPostTitle: title,
      DatabaseHelper.columnPostContent: content,
      DatabaseHelper.columnPostUserId: userId,
      DatabaseHelper.columnPostCreatedAt: createdAt.toIso8601String(),
    };
  }
  
  @override
  String toString() {
    return 'Post{id: $id, title: $title, content: $content, userId: $userId, createdAt: $createdAt}';
  }
}

// User with posts combined model
class UserWithPosts {
  final User user;
  final List<Post> posts;
  
  UserWithPosts({
    required this.user,
    required this.posts,
  });
  
  @override
  String toString() {
    return 'UserWithPosts{user: $user, posts: ${posts.length} posts}';
  }
}

Basic CRUD Operations

class UserRepository {
  // Create user
  static Future<int> insertUser(User user) async {
    final Database db = await DatabaseHelper.database;
    
    try {
      final Map<String, dynamic> userMap = user.toMap();
      userMap.remove(DatabaseHelper.columnUserId); // Remove ID for auto-increment
      
      int userId = await db.insert(
        DatabaseHelper.tableUsers,
        userMap,
        conflictAlgorithm: ConflictAlgorithm.abort,
      );
      
      print('User created: ID $userId');
      return userId;
    } catch (e) {
      print('User creation error: $e');
      rethrow;
    }
  }
  
  // Get all users
  static Future<List<User>> getAllUsers() async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tableUsers,
      orderBy: '${DatabaseHelper.columnUserCreatedAt} DESC',
    );
    
    return List.generate(maps.length, (i) {
      return User.fromMap(maps[i]);
    });
  }
  
  // Get user by ID
  static Future<User?> getUserById(int id) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tableUsers,
      where: '${DatabaseHelper.columnUserId} = ?',
      whereArgs: [id],
    );
    
    if (maps.isNotEmpty) {
      return User.fromMap(maps.first);
    }
    return null;
  }
  
  // Search user by email
  static Future<User?> getUserByEmail(String email) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tableUsers,
      where: '${DatabaseHelper.columnUserEmail} = ?',
      whereArgs: [email],
    );
    
    if (maps.isNotEmpty) {
      return User.fromMap(maps.first);
    }
    return null;
  }
  
  // Update user information
  static Future<bool> updateUser(User user) async {
    final Database db = await DatabaseHelper.database;
    
    try {
      int count = await db.update(
        DatabaseHelper.tableUsers,
        user.toMap(),
        where: '${DatabaseHelper.columnUserId} = ?',
        whereArgs: [user.id],
      );
      
      print('User updated: $count records');
      return count > 0;
    } catch (e) {
      print('User update error: $e');
      return false;
    }
  }
  
  // Delete user
  static Future<bool> deleteUser(int id) async {
    final Database db = await DatabaseHelper.database;
    
    try {
      // Delete related posts (cascade delete)
      await db.delete(
        DatabaseHelper.tablePosts,
        where: '${DatabaseHelper.columnPostUserId} = ?',
        whereArgs: [id],
      );
      
      int count = await db.delete(
        DatabaseHelper.tableUsers,
        where: '${DatabaseHelper.columnUserId} = ?',
        whereArgs: [id],
      );
      
      print('User deleted: $count records');
      return count > 0;
    } catch (e) {
      print('User deletion error: $e');
      return false;
    }
  }
  
  // Search users by age range
  static Future<List<User>> getUsersByAgeRange(int minAge, int maxAge) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tableUsers,
      where: '${DatabaseHelper.columnUserAge} BETWEEN ? AND ?',
      whereArgs: [minAge, maxAge],
      orderBy: '${DatabaseHelper.columnUserAge} ASC',
    );
    
    return List.generate(maps.length, (i) {
      return User.fromMap(maps[i]);
    });
  }
  
  // Count users
  static Future<int> getUserCount() async {
    final Database db = await DatabaseHelper.database;
    
    var result = await db.rawQuery('SELECT COUNT(*) FROM ${DatabaseHelper.tableUsers}');
    return Sqflite.firstIntValue(result) ?? 0;
  }
}

class PostRepository {
  // Create post
  static Future<int> insertPost(Post post) async {
    final Database db = await DatabaseHelper.database;
    
    try {
      final Map<String, dynamic> postMap = post.toMap();
      postMap.remove(DatabaseHelper.columnPostId);
      
      int postId = await db.insert(
        DatabaseHelper.tablePosts,
        postMap,
        conflictAlgorithm: ConflictAlgorithm.abort,
      );
      
      print('Post created: ID $postId');
      return postId;
    } catch (e) {
      print('Post creation error: $e');
      rethrow;
    }
  }
  
  // Get posts by user ID
  static Future<List<Post>> getPostsByUserId(int userId) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tablePosts,
      where: '${DatabaseHelper.columnPostUserId} = ?',
      whereArgs: [userId],
      orderBy: '${DatabaseHelper.columnPostCreatedAt} DESC',
    );
    
    return List.generate(maps.length, (i) {
      return Post.fromMap(maps[i]);
    });
  }
  
  // Get all posts
  static Future<List<Post>> getAllPosts() async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tablePosts,
      orderBy: '${DatabaseHelper.columnPostCreatedAt} DESC',
    );
    
    return List.generate(maps.length, (i) {
      return Post.fromMap(maps[i]);
    });
  }
  
  // Delete post
  static Future<bool> deletePost(int id) async {
    final Database db = await DatabaseHelper.database;
    
    try {
      int count = await db.delete(
        DatabaseHelper.tablePosts,
        where: '${DatabaseHelper.columnPostId} = ?',
        whereArgs: [id],
      );
      
      return count > 0;
    } catch (e) {
      print('Post deletion error: $e');
      return false;
    }
  }
}

Advanced Queries and JOIN Operations

class AdvancedQueries {
  // JOIN users and posts
  static Future<List<UserWithPosts>> getUsersWithPosts() async {
    final Database db = await DatabaseHelper.database;
    
    // Get users
    final users = await UserRepository.getAllUsers();
    
    List<UserWithPosts> result = [];
    for (User user in users) {
      final posts = await PostRepository.getPostsByUserId(user.id!);
      result.add(UserWithPosts(user: user, posts: posts));
    }
    
    return result;
  }
  
  // Complex JOIN query with raw SQL
  static Future<List<Map<String, dynamic>>> getUserPostStats() async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> result = await db.rawQuery('''
      SELECT 
        u.${DatabaseHelper.columnUserId} as user_id,
        u.${DatabaseHelper.columnUserName} as user_name,
        u.${DatabaseHelper.columnUserEmail} as user_email,
        COUNT(p.${DatabaseHelper.columnPostId}) as post_count,
        MAX(p.${DatabaseHelper.columnPostCreatedAt}) as latest_post_date
      FROM ${DatabaseHelper.tableUsers} u
      LEFT JOIN ${DatabaseHelper.tablePosts} p 
        ON u.${DatabaseHelper.columnUserId} = p.${DatabaseHelper.columnPostUserId}
      GROUP BY u.${DatabaseHelper.columnUserId}, u.${DatabaseHelper.columnUserName}, u.${DatabaseHelper.columnUserEmail}
      ORDER BY post_count DESC
    ''');
    
    return result;
  }
  
  // Search posts by date range
  static Future<List<Post>> getPostsByDateRange(DateTime startDate, DateTime endDate) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tablePosts,
      where: '${DatabaseHelper.columnPostCreatedAt} BETWEEN ? AND ?',
      whereArgs: [startDate.toIso8601String(), endDate.toIso8601String()],
      orderBy: '${DatabaseHelper.columnPostCreatedAt} DESC',
    );
    
    return List.generate(maps.length, (i) {
      return Post.fromMap(maps[i]);
    });
  }
  
  // Full-text search (using LIKE)
  static Future<List<Post>> searchPosts(String keyword) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tablePosts,
      where: '${DatabaseHelper.columnPostTitle} LIKE ? OR ${DatabaseHelper.columnPostContent} LIKE ?',
      whereArgs: ['%$keyword%', '%$keyword%'],
      orderBy: '${DatabaseHelper.columnPostCreatedAt} DESC',
    );
    
    return List.generate(maps.length, (i) {
      return Post.fromMap(maps[i]);
    });
  }
  
  // Pagination support
  static Future<List<Post>> getPostsPaginated(int page, int pageSize) async {
    final Database db = await DatabaseHelper.database;
    
    final List<Map<String, dynamic>> maps = await db.query(
      DatabaseHelper.tablePosts,
      orderBy: '${DatabaseHelper.columnPostCreatedAt} DESC',
      limit: pageSize,
      offset: page * pageSize,
    );
    
    return List.generate(maps.length, (i) {
      return Post.fromMap(maps[i]);
    });
  }
}

Transaction Processing and Batch Operations

class TransactionOperations {
  // Transaction usage example
  static Future<void> createUserWithPosts(User user, List<Post> posts) async {
    final Database db = await DatabaseHelper.database;
    
    await db.transaction((txn) async {
      try {
        // Create user
        final Map<String, dynamic> userMap = user.toMap();
        userMap.remove(DatabaseHelper.columnUserId);
        
        int userId = await txn.insert(
          DatabaseHelper.tableUsers,
          userMap,
        );
        
        // Create posts
        for (Post post in posts) {
          final Map<String, dynamic> postMap = post.toMap();
          postMap.remove(DatabaseHelper.columnPostId);
          postMap[DatabaseHelper.columnPostUserId] = userId;
          
          await txn.insert(
            DatabaseHelper.tablePosts,
            postMap,
          );
        }
        
        print('Created user and ${posts.length} posts');
      } catch (e) {
        print('Transaction error: $e');
        rethrow; // Rollback will be executed
      }
    });
  }
  
  // Batch operations
  static Future<void> batchInsertUsers(List<User> users) async {
    final Database db = await DatabaseHelper.database;
    
    Batch batch = db.batch();
    
    for (User user in users) {
      final Map<String, dynamic> userMap = user.toMap();
      userMap.remove(DatabaseHelper.columnUserId);
      
      batch.insert(
        DatabaseHelper.tableUsers,
        userMap,
      );
    }
    
    try {
      List<dynamic> results = await batch.commit(noResult: false);
      print('Batch processing completed: Created ${results.length} users');
    } catch (e) {
      print('Batch processing error: $e');
      rethrow;
    }
  }
  
  // Complex transaction example
  static Future<void> transferPostsBetweenUsers(int fromUserId, int toUserId) async {
    final Database db = await DatabaseHelper.database;
    
    await db.transaction((txn) async {
      // Check user existence
      final fromUser = await txn.query(
        DatabaseHelper.tableUsers,
        where: '${DatabaseHelper.columnUserId} = ?',
        whereArgs: [fromUserId],
      );
      
      final toUser = await txn.query(
        DatabaseHelper.tableUsers,
        where: '${DatabaseHelper.columnUserId} = ?',
        whereArgs: [toUserId],
      );
      
      if (fromUser.isEmpty || toUser.isEmpty) {
        throw Exception('Specified user not found');
      }
      
      // Transfer posts
      int updatedCount = await txn.update(
        DatabaseHelper.tablePosts,
        {DatabaseHelper.columnPostUserId: toUserId},
        where: '${DatabaseHelper.columnPostUserId} = ?',
        whereArgs: [fromUserId],
      );
      
      print('Transferred $updatedCount posts');
    });
  }
}

Practical Flutter Widget Integration

// User list screen
class UserListScreen extends StatefulWidget {
  @override
  _UserListScreenState createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  List<User> users = [];
  bool isLoading = true;
  
  @override
  void initState() {
    super.initState();
    _loadUsers();
  }
  
  Future<void> _loadUsers() async {
    try {
      final loadedUsers = await UserRepository.getAllUsers();
      setState(() {
        users = loadedUsers;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      _showErrorDialog('User loading error: $e');
    }
  }
  
  Future<void> _addUser() async {
    final user = User(
      name: 'Test User ${DateTime.now().millisecondsSinceEpoch}',
      email: 'test${DateTime.now().millisecondsSinceEpoch}@example.com',
      age: 25,
      createdAt: DateTime.now(),
    );
    
    try {
      await UserRepository.insertUser(user);
      _loadUsers(); // Reload list
    } catch (e) {
      _showErrorDialog('User creation error: $e');
    }
  }
  
  Future<void> _deleteUser(int userId) async {
    try {
      bool deleted = await UserRepository.deleteUser(userId);
      if (deleted) {
        _loadUsers(); // Reload list
      }
    } catch (e) {
      _showErrorDialog('User deletion error: $e');
    }
  }
  
  void _showErrorDialog(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Error'),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: Text('OK'),
          ),
        ],
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('User List'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: _loadUsers,
          ),
        ],
      ),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : users.isEmpty
              ? Center(child: Text('No users found'))
              : ListView.builder(
                  itemCount: users.length,
                  itemBuilder: (context, index) {
                    final user = users[index];
                    return ListTile(
                      leading: CircleAvatar(
                        child: Text(user.name.substring(0, 1)),
                      ),
                      title: Text(user.name),
                      subtitle: Text('${user.email} • Age: ${user.age ?? 'Unknown'}'),
                      trailing: IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () => _deleteUser(user.id!),
                      ),
                      onTap: () {
                        // Navigate to user detail screen
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => UserDetailScreen(user: user),
                          ),
                        );
                      },
                    );
                  },
                ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addUser,
        child: Icon(Icons.add),
      ),
    );
  }
}

// User detail screen
class UserDetailScreen extends StatefulWidget {
  final User user;
  
  UserDetailScreen({required this.user});
  
  @override
  _UserDetailScreenState createState() => _UserDetailScreenState();
}

class _UserDetailScreenState extends State<UserDetailScreen> {
  List<Post> posts = [];
  bool isLoading = true;
  
  @override
  void initState() {
    super.initState();
    _loadUserPosts();
  }
  
  Future<void> _loadUserPosts() async {
    try {
      final userPosts = await PostRepository.getPostsByUserId(widget.user.id!);
      setState(() {
        posts = userPosts;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.user.name),
      ),
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(16.0),
            child: Card(
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('User Information', style: Theme.of(context).textTheme.titleLarge),
                    SizedBox(height: 8),
                    Text('Name: ${widget.user.name}'),
                    Text('Email: ${widget.user.email}'),
                    Text('Age: ${widget.user.age ?? 'Unknown'}'),
                    Text('Created: ${widget.user.createdAt.toString().substring(0, 19)}'),
                  ],
                ),
              ),
            ),
          ),
          Expanded(
            child: isLoading
                ? Center(child: CircularProgressIndicator())
                : posts.isEmpty
                    ? Center(child: Text('No posts found'))
                    : ListView.builder(
                        itemCount: posts.length,
                        itemBuilder: (context, index) {
                          final post = posts[index];
                          return Card(
                            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
                            child: ListTile(
                              title: Text(post.title),
                              subtitle: Text(
                                post.content.length > 100
                                    ? '${post.content.substring(0, 100)}...'
                                    : post.content,
                              ),
                              trailing: Text(
                                post.createdAt.toString().substring(0, 16),
                                style: Theme.of(context).textTheme.bodySmall,
                              ),
                            ),
                          );
                        },
                      ),
          ),
        ],
      ),
    );
  }
}