Flutter 分页和提供程序:在NotificationListener 中构建期间调用 setState() 或 markNeedsBuild()

问题描述 投票:0回答:1

我目前正在 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();
  }
}
flutter pagination setstate infinite-scroll
1个回答
0
投票

在构建函数中调用提供程序时,您应该设置

listen: false
示例:

bool hasMore = Provider.of<MediaProvider>(context, listen: false).hasMore[widget.album.albumId] ?? true;
© www.soinside.com 2019 - 2024. All rights reserved.