Flutter中如何展示从Firebase实时数据库获取的最近播放歌曲
最近播放歌曲展示方案(Flutter + Firebase)
一、数据层处理(核心逻辑)
1. Firebase数据结构设计
在实时数据库中给每个用户单独维护一个recentlyPlayed节点,每条记录包含:
songId: 歌曲唯一标识playTime: 播放时间戳(用于排序)- 可选:歌曲名称、歌手、封面URL(避免每次拉取详情,提升加载速度)
示例结构:
"users": { "userId_123": { "recentlyPlayed": { "record_01": { "songId": "song_001", "playTime": 1699999999000, "title": "XXX", "artist": "YYY", "coverUrl": "https://xxx.com/cover.jpg" }, "record_02": { // ... 其他歌曲数据 } } } }
2. 更新最近播放记录
每次播放歌曲时,向recentlyPlayed节点写入新记录,同时限制只保留最近10条(可自定义数量):
import 'package:firebase_database/firebase_database.dart'; Future<void> updateRecentlyPlayed(String userId, Map<String, dynamic> songData) async { final ref = FirebaseDatabase.instance.ref('users/$userId/recentlyPlayed'); // 先获取当前已有记录数量 final snapshot = await ref.limitToLast(10).get(); if (snapshot.children.length >= 10) { // 删除最早的一条记录 final oldestKey = snapshot.children.first.key; await ref.child(oldestKey!).remove(); } // 添加新记录(用时间戳作为键,保证唯一性) await ref.child('${DateTime.now().millisecondsSinceEpoch}').set({ ...songData, 'playTime': DateTime.now().millisecondsSinceEpoch, }); }
3. 获取最近播放列表
通过时间戳倒序获取最近N条记录,用StreamBuilder监听实时更新:
Stream<List<Map<String, dynamic>>> getRecentlyPlayedStream(String userId) { final ref = FirebaseDatabase.instance.ref('users/$userId/recentlyPlayed') .orderByChild('playTime') .limitToLast(10); return ref.onValue.map((event) { final data = event.snapshot.value as Map<dynamic, dynamic>?; if (data == null) return []; // 转换为列表并反转(因为orderByChild是升序,反转后变成最新的在前) final list = data.values.map((e) => Map<String, dynamic>.from(e)).toList(); list.sort((a, b) => b['playTime'].compareTo(a['playTime'])); return list; }); }
二、UI层展示
1. 基础列表实现
用ListView.builder配合StreamBuilder实现实时更新的列表:
StreamBuilder<List<Map<String, dynamic>>>( stream: getRecentlyPlayedStream('当前用户ID'), builder: (context, snapshot) { if (!snapshot.hasData) return const Center(child: CircularProgressIndicator()); final songs = snapshot.data!; if (songs.isEmpty) return const Center(child: Text('暂无最近播放')); return ListView.builder( itemCount: songs.length, itemBuilder: (context, index) { final song = songs[index]; return ListTile( leading: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( song['coverUrl'], width: 50, height: 50, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon(Icons.music_note), ), ), title: Text(song['title']), subtitle: Text(song['artist']), onTap: () { // 触发播放逻辑(已有播放功能,直接调用对应方法) playSong(song['songId']); }, ); }, ); }, )
2. 增强UI体验
- 图片缓存:用
cached_network_image包替代Image.network,避免重复加载网络图片CachedNetworkImage( imageUrl: song['coverUrl'], width: 50, height: 50, fit: BoxFit.cover, placeholder: (_, __) => const Icon(Icons.music_note), errorWidget: (_, __, ___) => const Icon(Icons.music_note), ) - 滑动删除:用
flutter_slidable实现滑动删除最近播放条目Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), children: [ SlidableAction( onPressed: (_) => deleteRecentRecord('当前用户ID', songs[index]['recordKey']), backgroundColor: Colors.red, foregroundColor: Colors.white, icon: Icons.delete, label: '删除', ), ], ), child: ListTile(/* ... 原有内容 ... */), )
三、推荐包总结
- 核心数据操作:
firebase_database(官方包,稳定可靠,直接对接实时数据库) - 图片缓存:
cached_network_image(提升图片加载性能) - 交互增强:
flutter_slidable(滑动操作)、pull_to_refresh(下拉刷新,按需使用) - 本地缓存(可选):
hive(适合存储结构化的最近播放数据,离线时也能展示)
内容的提问来源于stack exchange,提问作者Lavina Daryanani
相关产品推荐
相关产品推荐

