SQLite (sqflite)

sqfliteはFlutter向けの軽量で高性能なSQLiteデータベースライブラリで、「iOS、Android、macOSでのローカルデータ永続化の決定版」として位置づけられています。Flutterエコシステムにおいて事実上の標準的なSQLite実装として、ネイティブパフォーマンスと型安全性を両立し、モバイルアプリケーションでの構造化データ管理に特化した包括的なソリューションを提供します。

FlutterDartSQLiteモバイルデータベースローカルストレージ

ライブラリ

SQLite (sqflite)

概要

sqfliteはFlutter向けの軽量で高性能なSQLiteデータベースライブラリで、「iOS、Android、macOSでのローカルデータ永続化の決定版」として位置づけられています。Flutterエコシステムにおいて事実上の標準的なSQLite実装として、ネイティブパフォーマンスと型安全性を両立し、モバイルアプリケーションでの構造化データ管理に特化した包括的なソリューションを提供します。

詳細

sqflite 2025年版は、Flutter 3.0以降の最新機能と完全統合し、iOS、Android、macOS、およびWeb(実験的)での一貫したSQLiteデータベース体験を実現します。ネイティブSQLiteエンジンとの直接統合により、高いパフォーマンスとメモリ効率を維持しながら、Dart FutureベースのAPIとasync/awaitサポートによる現代的な非同期プログラミングパターンを提供。データベーススキーマ管理、マイグレーション、バッチ処理、トランザクション制御を包括的にサポートし、エンタープライズレベルのモバイルアプリケーション開発に対応します。

主な特徴

  • マルチプラットフォーム対応: iOS、Android、macOS、Web(実験的)での統一API
  • 高性能ネイティブ実装: 直接SQLiteエンジン統合による最適化されたパフォーマンス
  • 非同期処理サポート: Future/async-awaitベースの現代的なAPI設計
  • 包括的CRUD機能: 完全なCRUD操作とSQL文実行サポート
  • スキーマ管理: データベースバージョニングとマイグレーション機能
  • トランザクション制御: ACID特性保証による安全なデータ操作

メリット・デメリット

メリット

  • Flutterエコシステムでの標準的地位と豊富な学習リソース
  • ネイティブパフォーマンスによる高速なデータベース操作
  • 軽量な実装でアプリサイズへの影響最小化
  • 堅牢なACID特性による安全なトランザクション処理
  • 直感的なDart APIによる低い学習コストと高い開発効率
  • 豊富なコミュニティサポートとサードパーティ統合

デメリット

  • SQLiteの制約による高度なリレーショナル機能の限界
  • ローカルストレージのみでクラウド同期機能は別途実装が必要
  • 大容量データセットでのパフォーマンス制約
  • Web実装は実験的段階で制限事項が存在
  • 複雑なクエリ構築時の生SQL記述による可読性課題
  • NoSQLデータモデルには不適合

参考ページ

書き方の例

プロジェクトセットアップと依存関係

# 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';

データベース初期化とセットアップ

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

class DatabaseHelper {
  static const _databaseName = "app_database.db";
  static const _databaseVersion = 1;
  
  // テーブル定義
  static const String tableUsers = 'users';
  static const String tablePosts = 'posts';
  
  // ユーザーテーブルのカラム
  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';
  
  // 投稿テーブルのカラム
  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;
  
  // シングルトンパターンでデータベースインスタンス管理
  static Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }
  
  // データベース初期化
  static Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), _databaseName);
    
    return await openDatabase(
      path,
      version: _databaseVersion,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }
  
  // テーブル作成
  static Future<void> _onCreate(Database db, int version) async {
    // ユーザーテーブル作成
    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
      )
    ''');
    
    // 投稿テーブル作成
    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)
      )
    ''');
    
    // インデックス作成
    await db.execute('''
      CREATE INDEX idx_posts_user_id ON $tablePosts($columnPostUserId)
    ''');
    
    await db.execute('''
      CREATE INDEX idx_users_email ON $tableUsers($columnUserEmail)
    ''');
  }
  
  // データベースアップグレード
  static Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 2) {
      // バージョン2のマイグレーション例
      await db.execute('''
        ALTER TABLE $tableUsers ADD COLUMN profile_image TEXT
      ''');
    }
    
    if (oldVersion < 3) {
      // バージョン3のマイグレーション例
      await db.execute('''
        CREATE TABLE categories (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL,
          description TEXT
        )
      ''');
    }
  }
  
  // データベース接続終了
  static Future<void> closeDatabase() async {
    final db = _database;
    if (db != null) {
      await db.close();
      _database = null;
    }
  }
}

モデルクラス定義

// ユーザーモデル
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,
  });
  
  // データベースからのマップ変換
  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<String, dynamic> toMap() {
    return {
      DatabaseHelper.columnUserId: id,
      DatabaseHelper.columnUserName: name,
      DatabaseHelper.columnUserEmail: email,
      DatabaseHelper.columnUserAge: age,
      DatabaseHelper.columnUserCreatedAt: createdAt.toIso8601String(),
    };
  }
  
  // デバッグ用文字列表現
  @override
  String toString() {
    return 'User{id: $id, name: $name, email: $email, age: $age, createdAt: $createdAt}';
  }
  
  // コピーウィズメソッド
  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,
    );
  }
}

// 投稿モデル
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}';
  }
}

// ユーザーと投稿の結合モデル
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}';
  }
}

基本的なCRUD操作

class UserRepository {
  // ユーザー作成
  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); // auto-incrementのためIDを除去
      
      int userId = await db.insert(
        DatabaseHelper.tableUsers,
        userMap,
        conflictAlgorithm: ConflictAlgorithm.abort,
      );
      
      print('ユーザーが作成されました: ID $userId');
      return userId;
    } catch (e) {
      print('ユーザー作成エラー: $e');
      rethrow;
    }
  }
  
  // 全ユーザー取得
  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]);
    });
  }
  
  // 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;
  }
  
  // メールでユーザー検索
  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;
  }
  
  // ユーザー情報更新
  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('ユーザー更新: $count 件');
      return count > 0;
    } catch (e) {
      print('ユーザー更新エラー: $e');
      return false;
    }
  }
  
  // ユーザー削除
  static Future<bool> deleteUser(int id) async {
    final Database db = await DatabaseHelper.database;
    
    try {
      // 関連する投稿も削除(カスケード削除)
      await db.delete(
        DatabaseHelper.tablePosts,
        where: '${DatabaseHelper.columnPostUserId} = ?',
        whereArgs: [id],
      );
      
      int count = await db.delete(
        DatabaseHelper.tableUsers,
        where: '${DatabaseHelper.columnUserId} = ?',
        whereArgs: [id],
      );
      
      print('ユーザー削除: $count 件');
      return count > 0;
    } catch (e) {
      print('ユーザー削除エラー: $e');
      return false;
    }
  }
  
  // 年齢範囲でユーザー検索
  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]);
    });
  }
  
  // ユーザー数カウント
  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 {
  // 投稿作成
  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('投稿が作成されました: ID $postId');
      return postId;
    } catch (e) {
      print('投稿作成エラー: $e');
      rethrow;
    }
  }
  
  // ユーザーの投稿取得
  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]);
    });
  }
  
  // 全投稿取得
  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]);
    });
  }
  
  // 投稿削除
  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('投稿削除エラー: $e');
      return false;
    }
  }
}

高度なクエリとJOIN操作

class AdvancedQueries {
  // ユーザーと投稿のJOIN
  static Future<List<UserWithPosts>> getUsersWithPosts() async {
    final Database db = await DatabaseHelper.database;
    
    // ユーザー取得
    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;
  }
  
  // 生SQLでの複雑なJOINクエリ
  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;
  }
  
  // 日付範囲での投稿検索
  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]);
    });
  }
  
  // フルテキスト検索(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]);
    });
  }
  
  // ページネーション対応
  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]);
    });
  }
}

トランザクション処理とバッチ操作

class TransactionOperations {
  // トランザクション使用例
  static Future<void> createUserWithPosts(User user, List<Post> posts) async {
    final Database db = await DatabaseHelper.database;
    
    await db.transaction((txn) async {
      try {
        // ユーザー作成
        final Map<String, dynamic> userMap = user.toMap();
        userMap.remove(DatabaseHelper.columnUserId);
        
        int userId = await txn.insert(
          DatabaseHelper.tableUsers,
          userMap,
        );
        
        // 投稿作成
        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('ユーザーと${posts.length}件の投稿を作成しました');
      } catch (e) {
        print('トランザクションエラー: $e');
        rethrow; // ロールバックが実行される
      }
    });
  }
  
  // バッチ操作
  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('バッチ処理完了: ${results.length}件のユーザーを作成');
    } catch (e) {
      print('バッチ処理エラー: $e');
      rethrow;
    }
  }
  
  // 複雑なトランザクション例
  static Future<void> transferPostsBetweenUsers(int fromUserId, int toUserId) async {
    final Database db = await DatabaseHelper.database;
    
    await db.transaction((txn) async {
      // ユーザー存在確認
      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('指定されたユーザーが見つかりません');
      }
      
      // 投稿の移転
      int updatedCount = await txn.update(
        DatabaseHelper.tablePosts,
        {DatabaseHelper.columnPostUserId: toUserId},
        where: '${DatabaseHelper.columnPostUserId} = ?',
        whereArgs: [fromUserId],
      );
      
      print('$updatedCount件の投稿を移転しました');
    });
  }
}

実用的なFlutterウィジェット統合

// ユーザー一覧画面
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('ユーザー読み込みエラー: $e');
    }
  }
  
  Future<void> _addUser() async {
    final user = User(
      name: 'テストユーザー ${DateTime.now().millisecondsSinceEpoch}',
      email: 'test${DateTime.now().millisecondsSinceEpoch}@example.com',
      age: 25,
      createdAt: DateTime.now(),
    );
    
    try {
      await UserRepository.insertUser(user);
      _loadUsers(); // リストを再読み込み
    } catch (e) {
      _showErrorDialog('ユーザー作成エラー: $e');
    }
  }
  
  Future<void> _deleteUser(int userId) async {
    try {
      bool deleted = await UserRepository.deleteUser(userId);
      if (deleted) {
        _loadUsers(); // リストを再読み込み
      }
    } catch (e) {
      _showErrorDialog('ユーザー削除エラー: $e');
    }
  }
  
  void _showErrorDialog(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('エラー'),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: Text('OK'),
          ),
        ],
      ),
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ユーザー一覧'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: _loadUsers,
          ),
        ],
      ),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : users.isEmpty
              ? Center(child: Text('ユーザーがいません'))
              : 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} • 年齢: ${user.age ?? '不明'}'),
                      trailing: IconButton(
                        icon: Icon(Icons.delete),
                        onPressed: () => _deleteUser(user.id!),
                      ),
                      onTap: () {
                        // ユーザー詳細画面への遷移
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => UserDetailScreen(user: user),
                          ),
                        );
                      },
                    );
                  },
                ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addUser,
        child: Icon(Icons.add),
      ),
    );
  }
}

// ユーザー詳細画面
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('ユーザー情報', style: Theme.of(context).textTheme.titleLarge),
                    SizedBox(height: 8),
                    Text('名前: ${widget.user.name}'),
                    Text('メール: ${widget.user.email}'),
                    Text('年齢: ${widget.user.age ?? '不明'}'),
                    Text('作成日: ${widget.user.createdAt.toString().substring(0, 19)}'),
                  ],
                ),
              ),
            ),
          ),
          Expanded(
            child: isLoading
                ? Center(child: CircularProgressIndicator())
                : posts.isEmpty
                    ? Center(child: Text('投稿がありません'))
                    : 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,
                              ),
                            ),
                          );
                        },
                      ),
          ),
        ],
      ),
    );
  }
}