我目前正在 Flutter 中为图像库构建 GridView,利用提供程序来管理我的媒体列表并集成缓存系统。 GridView 设计用于分页,其中前 30 个项目将被获取并显示,并且仅当用户滚动到 ListView 底部时才会获取其他项目。 GridView 收缩包装在 ListView 内部,以容纳屏幕上的其他小部件。
我遇到一个错误,指出“在 NotificiationListener 中构建期间调用了 setState() 或 markNeedsBuild()”。我设置的逻辑涉及我的 MediaProvider 更新变量,如果集合中的文档数量与获取的数量匹配,则“hasMore”为 false。当 hasMore 为 false 时,onNotification 方法返回 false,这应该终止任何进一步的媒体加载,直到下一个应用程序生命周期。
我预计一旦 hasMore 设置为 false(显然是在构建阶段),更多媒体的加载将终止,而不会触发任何构建错误,特别是因为我将其包装在 WidgetsBinding.instance.addPostFrameCallback() 中。
我读过的一些其他解决方案表明问题可能是由于 notifyListeners() 造成的,它是 setState() 的一种形式。但是,我不知道如果没有它我还能如何实现此功能:/.
class ImageGalleryScreen extends StatefulWidget {
const ImageGalleryScreen({super.key, required this.album});
final Album album;
@override
State<ImageGalleryScreen> createState() => _ImageGalleryScreenState();
}
class _ImageGalleryScreenState extends State<ImageGalleryScreen> {
late MediaProvider mediaProvider;
@override
void initState() {
mediaProvider = Provider.of<MediaProvider>(context, listen: false);
mediaProvider.initializeAlbumMedia(widget.album.albumId);
super.initState();
}
@override
Widget build(BuildContext context) {
bool hasMore =
Provider.of<MediaProvider>(context).hasMore[widget.album.albumId] ??
true;
return Scaffold(
appBar: AppBar(
title: const Text("Image Gallery"),
),
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
final metrics = scrollInfo.metrics;
if (hasMore && metrics.pixels == metrics.maxScrollExtent) {
mediaProvider.fetchMoreMedia(widget.album.albumId);
}
return hasMore;
},
child: ListView(
children: [
const Card(
child: ListTile(
title: Text("Album card"),
subtitle: Text("Album details and about info"),
trailing: Icon(Icons.arrow_forward_ios),
),
),
const SizedBox(height: 20),
Consumer<MediaProvider>(
builder: (context, provider, child) {
final mediaList = provider.mediaMap[widget.album.albumId] ?? [];
if (mediaList.isEmpty) {
return const SizedBox(
width: double.infinity,
child: Center(
child: Text("No Images Available."),
),
);
}
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0,
),
itemCount: mediaList.length,
itemBuilder: (context, index) {
final media = mediaList[index];
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
image: CachedNetworkImageProvider(media.downloadUrl),
fit: BoxFit.cover,
),
),
);
},
);
},
),
const SizedBox(height: 20),
Visibility(
visible: hasMore,
child: const Center(
child: CircularProgressIndicator(),
),
),
],
),
),
);
}
}
class ImageProvider extends ChangeNotifier {
static final ImageProvider _instance = ImageProvider._internal();
ImageProvider._internal();
factory ImageProvider() => _instance;
Map<String, List<Media>> get mediaMap => _mediaMap;
final Map<String, List<Media>> _mediaMap = {};
Map<String, int> get mediaCountMap => _mediaCountMap;
final Map<String, int> _mediaCountMap = {};
Map<String, bool> get hasMore => _hasMore;
final Map<String, bool> _hasMore = {};
final int _limit = 30;
final Map<String, StreamSubscription<QuerySnapshot>> _subscription = {};
final Map<String, DocumentSnapshot?> _lastDocInMap = {};
void initAlbumMedia(String albumId) {
_getMediaCount(albumId);
_startMediaSubscription(albumId);
}
void _getMediaCount(String albumId) async {
final snap =
await FirebaseFirestore.instance.collection('Images').count().get();
final count = snap.count ?? 0;
_mediaCountMap[albumId] = count;
if (count > _limit) {
_hasMore[albumId] = true;
}
notifyListeners();
}
void _startMediaSubscription(String albumId) async {
if (_subscription.containsKey(albumId)) return;
final subscription = FirebaseFirestore.instance
.collection('Images')
.orderBy('dateTime', descending: true)
.limit(_limit)
.snapshots()
.listen((snap) {
_mediaMap[albumId] =
snap.docs.map((doc) => Media.fromMap(doc.data())).toList();
_lastDocInMap[albumId] = snap.docs.isNotEmpty ? snap.docs.last : null;
});
_subscription[albumId] = subscription;
notifyListeners();
}
void fetchMoreMedia(String albumId) async {
final lastDoc = _lastDocInMap[albumId];
if (lastDoc == null) return;
final newMedia = await FirebaseFirestore.instance
.collection('Images')
.orderBy('dateTime', descending: true)
.startAfterDocument(lastDoc)
.limit(9)
.get()
.then(
(snap) {
if (snap.docs.isEmpty) return null;
_lastDocInMap[albumId] = snap.docs.last;
return snap.docs.map((doc) => Media.fromMap(doc.data())).toList();
},
);
if (newMedia == null) {
_hasMore[albumId] = false;
} else {
_mediaMap[albumId]?.addAll(newMedia);
}
notifyListeners();
}
}
在构建函数中调用提供程序时,您应该设置
listen: false
示例:
bool hasMore = Provider.of<MediaProvider>(context, listen: false).hasMore[widget.album.albumId] ?? true;