如何在Flutter中基于SQFlite数据库创建StreamBuilder?
实现SQFlite数据的StreamBuilder自动更新ListView
要让ListView在CRUD操作时自动更新,核心是把原有的Future<List<Message>>转换成持续推送最新数据的Stream,再用StreamBuilder监听这个流。以下是具体实现步骤:
1. 改造数据库类,添加Stream控制器
在你的数据库操作类中,新增一个StreamController来管理消息数据流,同时在每次CRUD操作后推送最新数据:
class MessageDatabase { Database? _db; // 使用broadcast控制器支持多监听者 final StreamController<List<Message>> _messageStreamController = StreamController<List<Message>>.broadcast(); // 对外暴露消息流 Stream<List<Message>> get messageStream => _messageStreamController.stream; // 初始化数据库并推送初始数据 Future<void> initDatabase() async { // 原有数据库初始化逻辑(比如打开/创建数据库) _db = await openDatabase( 'messages.db', version: 1, onCreate: (db, version) async { await db.execute(''' CREATE TABLE Test ( inboxId TEXT, messageId TEXT PRIMARY KEY, sticker TEXT, timestamp INTEGER ) '''); }, ); // 推送初始数据到流 final initialMessages = await _fetchMessages(); _messageStreamController.add(initialMessages); } // 原有数据查询方法,改为私有方法供内部调用 Future<List<Message>> _fetchMessages() async { final List<Map<String, dynamic>> maps = await _db!.query('Test', orderBy: 'timestamp DESC'); return List.generate(maps.length, (i) { return Message( inboxId: maps[i]['inboxId'], messageId: maps[i]['messageId'], sticker: maps[i]['sticker'], ); }); } // 新增消息:操作完成后推送最新数据 Future<void> insertMessage(Message message) async { await _db!.insert( 'Test', message.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); _messageStreamController.add(await _fetchMessages()); } // 更新消息:操作完成后推送最新数据 Future<void> updateMessage(Message message) async { await _db!.update( 'Test', message.toMap(), where: 'messageId = ?', whereArgs: [message.messageId], ); _messageStreamController.add(await _fetchMessages()); } // 删除消息:操作完成后推送最新数据 Future<void> deleteMessage(String messageId) async { await _db!.delete( 'Test', where: 'messageId = ?', whereArgs: [messageId], ); _messageStreamController.add(await _fetchMessages()); } // 销毁控制器,避免内存泄漏 void dispose() { _messageStreamController.close(); } }
2. 实现Message类的toMap方法
确保Message类能转换成数据库存储的Map格式:
class Message { final String inboxId; final String messageId; final String? sticker; Message({ required this.inboxId, required this.messageId, this.sticker, }); // 转换为数据库可用的Map Map<String, dynamic> toMap() { return { 'inboxId': inboxId, 'messageId': messageId, 'sticker': sticker, 'timestamp': DateTime.now().millisecondsSinceEpoch, }; } }
3. 使用StreamBuilder构建ListView
在页面中监听消息流,自动更新列表:
class MessageListScreen extends StatefulWidget { @override _MessageListScreenState createState() => _MessageListScreenState(); } class _MessageListScreenState extends State<MessageListScreen> { late MessageDatabase _db; @override void initState() { super.initState(); _db = MessageDatabase(); _db.initDatabase(); } @override void dispose() { _db.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('消息列表')), body: StreamBuilder<List<Message>>( stream: _db.messageStream, builder: (context, snapshot) { // 加载状态 if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } // 无数据状态 if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center(child: Text('暂无消息')); } // 渲染列表 return ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (context, index) { final message = snapshot.data![index]; return ListTile( title: Text('消息ID: ${message.messageId}'), subtitle: Text('收件箱ID: ${message.inboxId}'), trailing: message.sticker != null ? Text('贴纸: ${message.sticker}') : null, ); }, ); }, ), // 测试用:添加新消息按钮 floatingActionButton: FloatingActionButton( onPressed: () { _db.insertMessage(Message( inboxId: 'inbox_001', messageId: 'msg_${DateTime.now().millisecondsSinceEpoch}', sticker: 'smile', )); }, child: const Icon(Icons.add), ), ); } }
关键注意事项
- 使用
broadcast类型的StreamController,这样多个组件可以同时监听同一个数据流。 - 每次CRUD操作完成后必须调用
_messageStreamController.add(最新数据),触发StreamBuilder重建列表。 - 在页面销毁时调用
dispose()关闭StreamController,防止内存泄漏。
内容的提问来源于stack exchange,提问作者tailor
相关产品推荐
相关产品推荐

