提供者对象在现有构建期间请求重建

Posted

技术标签:

【中文标题】提供者对象在现有构建期间请求重建【英文标题】:Provider Object Requests Rebuild During Existing Build 【发布时间】:2020-11-15 11:28:02 【问题描述】:

我正在学习 Provider,我的测试应用程序将图像从 Firestore 数据库绘制到 ListView 中。我想在 any 图像上使用 LongPress 来制作 whole 列表 toggle 并使用复选框选择器图标重绘,如下所示,类似于方式画廊作品:

我的代码可以工作,但它会在每个 LongPress 上抛出一个异常,指出“setState() 或 markNeedsBuild() 在构建期间被调用”,我正在努力尝试弄清楚如何延迟 ChangeNotifier 直到构建小部件树?或者其他方式来完成这项任务?

我的 Provider 类只接受我的 PictureModel 类的 List 并有一个通知听众的 toggleSelectors() 方法。代码如下:

class PicturesProvider with ChangeNotifier 
  List<PictureModel> _pictures = [];
  bool visible = false;

  UnmodifiableListView<PictureModel> get allPictures => UnmodifiableListView(_pictures);
  UnmodifiableListView<PictureModel> get selectedPictures =>
      UnmodifiableListView(_pictures.where((pic) => pic.selected));

  void addPictures(List<PictureModel> picList) 
    _pictures.addAll(picList);
    notifyListeners();
  

  void toggleSelectors() 
    visible = !visible;
    _pictures.forEach((pic) 
      pic.selectVisible = visible;
    );
    notifyListeners();
  


我有一个 SinglePicture UI 类,它将网络图像加载到 AspectRatio 小部件中,并用 GestureDetector 包装它以切换选择器并将它们呈现在 Stack 小部件的顶部,如下所示:

Widget build(BuildContext context) 
    int originalHeight, originalWidth;
    return AspectRatio(
      aspectRatio: pictureModel.aspectRatio,
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          FutureBuilder<ui.Image>(
            future: _getImage(),
            builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) 
              if (snapshot.hasData) 
                ui.Image image = snapshot.data;
                originalHeight = image.height;
                originalWidth = image.width;
                return GestureDetector(
                  onLongPress: () => Provider.of<PicturesProvider>(context, listen: false).toggleSelectors(),
                  child: RawImage(
                    image: image,
                    fit: BoxFit.cover,
                    // if portrait image, move down slightly for headroom
                    alignment: Alignment(0, originalHeight > originalWidth ? -0.2 : 0),
                  ),
                );
               else 
                return Center(child: CircularProgressIndicator());
              
            ,
          ),
          Positioned(
            left: 10.0,
            top: 10.0,
            child: pictureModel.selectVisible == false
                ? Container(
                    height: 0.0,
                    width: 0.0,
                  )
                : pictureModel.selected == false
                    ? Icon(
                        Icons.check_box_outline_blank,
                        size: 30.0,
                        color: Colors.white,
                      )
                    : Icon(
                        Icons.check_box,
                        size: 30.0,
                        color: Colors.white,
                      ),
          )
        ],
      ),
    );
  

然后从我的 PicturesList UI 类调用这个 SinglePicture 类,该类只构建一个 ListView,如下所示:

class PicturesList extends StatelessWidget 
  final List<PictureModel> pictures;
  PicturesList(@required this.pictures);
  @override
  Widget build(BuildContext context) 
    return ListView.builder(
      itemCount: pictures.length,
      cacheExtent: 3,
      itemBuilder: (context, index) 
        return SinglePicture(
          pictureModel: pictures[index],
        );
      ,
    );
  

然后从我的应用程序中的 FutureBuilder 调用整个 shebang,它构建应用程序,如下所示:

body: FutureBuilder(
  future: appProject.fetchProject(), // Snapshot of database
  builder: (BuildContext context, AsyncSnapshot snapshot) 
    if (snapshot.hasData) 
      // Get all picture URLs from project snapshot
      List<dynamic> picUrls = snapshot.data['pictures'].map((pic) => pic['pic_cloud']).toList();
      // Create list of PictureModel objects for Provider
      List<PictureModel> pictures = picUrls.map((url) => PictureModel(imageUrl: url, imageHeight: 250.0, selectVisible: false)).toList();
      // Add list of PictureModel objects to Provider for UI render
      context.watch<PicturesProvider>().addPictures(pictures);
      return SafeArea(
        child: PicturesList(
          pictures: context.watch<PicturesProvider>().allPictures,
        ),
      );
     else if (snapshot.hasError) 
      print('Error');
     else 
      return Center(
        child: CircularProgressIndicator(),
      );
    
  ,
),

如果有人提示我如何在不引发异常的情况下完成此切换操作,我将不胜感激。提前谢谢!

【问题讨论】:

你误用了 FutureBuilder。请参阅***.com/questions/52249578/… 了解更多信息 @RémiRousselet 谢谢您,先生。这解释了很多在我的代码中有点古怪的事情。现在唯一的问题是我必须回去重新考虑我所做的一切,以确保我的构建器方法是干净的。它虽然有效,但我会发布更新的代码供其他人查看,以防它有帮助。再次感谢! 【参考方案1】:

感谢 Remi Rousselet 的回答:

我从一开始就一直使用错误的 .builder 方法,现在需要重新检查我的所有代码并确保它们是干净的。

为了使这段代码正常工作,我将 Future 从我的 FutureBuilder 中移出,并按照 Remi 的指导在 initState 方法中调用它。我还必须在我的 Provider 类中创建一个新的初始化方法,它不会通知侦听器,所以我可以第一次构建列表。

这里是代码 sn-ps 使我的图像可以使用 LongPress '可选择'并能够通过点击单独选择它们,如下图所示:

My PictureModel:

class PictureModel 
  final String imageUrl;
  final double aspectRatio;
  double imageHeight;
  bool selectVisible;
  bool selected;

  PictureModel(
    @required this.imageUrl,
    @required this.imageHeight,
    this.aspectRatio = 4.0 / 3.0,
    this.selectVisible = false,
    this.selected = false,
  );

  @override
  String toString() 
    return 'Image URL: $this.imageUrl\n'
        'Image Height: $this.imageHeight\n'
        'Aspect Ratio: $this.aspectRatio\n'
        'Select Visible: $this.selectVisible\n'
        'Selected: $this.selected\n';
  

My PictureProvider 模型:

class PicturesProvider with ChangeNotifier 
  List<PictureModel> _pictures = [];
  bool visible = false;

  UnmodifiableListView<PictureModel> get allPictures => UnmodifiableListView(_pictures);
  UnmodifiableListView<PictureModel> get selectedPictures =>
      UnmodifiableListView(_pictures.where((pic) => pic.selected));

  void initialize(List<PictureModel> picList) 
    _pictures.addAll(picList);
  

  void addPictures(List<PictureModel> picList) 
    _pictures.addAll(picList);
    notifyListeners();
  

  void toggleSelected(int index) 
    _pictures[index].selected = !_pictures[index].selected;
    notifyListeners();
  

  void toggleSelectors() 
    this.visible = !this.visible;
    _pictures.forEach((pic) 
      pic.selectVisible = visible;
    );
    notifyListeners();
  

我的 SinglePicture UI 类:

class SinglePicture extends StatelessWidget 
  final PictureModel pictureModel;
  const SinglePicture(Key key, this.pictureModel) : super(key: key);

  Future<ui.Image> _getImage() 
    Completer<ui.Image> completer = new Completer<ui.Image>();
    new NetworkImage(pictureModel.imageUrl).resolve(new ImageConfiguration()).addListener(
      new ImageStreamListener(
        (ImageInfo image, bool _) 
          completer.complete(image.image);
        ,
      ),
    );
    return completer.future;
  

  @override
  Widget build(BuildContext context) 
    int originalHeight, originalWidth;
    return AspectRatio(
      aspectRatio: pictureModel.aspectRatio,
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          FutureBuilder<ui.Image>(
            future: _getImage(),
            builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) 
              if (snapshot.hasData) 
                ui.Image image = snapshot.data;
                originalHeight = image.height;
                originalWidth = image.width;
                return RawImage(
                  image: image,
                  fit: BoxFit.cover,
                  // if portrait image, move down slightly for headroom
                  alignment: Alignment(0, originalHeight > originalWidth ? -0.2 : 0),
                );
               else 
                return Center(child: CircularProgressIndicator());
              
            ,
          ),
          Positioned(
            left: 10.0,
            top: 10.0,
            child: pictureModel.selectVisible == false
                ? Container(
                    height: 0.0,
                    width: 0.0,
                  )
                : pictureModel.selected == false
                    ? Icon(
                        Icons.check_box_outline_blank,
                        size: 30.0,
                        color: Colors.white,
                      )
                    : Icon(
                        Icons.check_box,
                        size: 30.0,
                        color: Colors.white,
                      ),
          )
        ],
      ),
    );
  


My PicturesList UI 类:

class PicturesList extends StatelessWidget 
  PicturesList(this.listOfPics);
  final List<PictureModel> listOfPics;
  @override
  Widget build(BuildContext context) 
    context.watch<PicturesProvider>().initialize(listOfPics);
    final List<PictureModel> pictures = context.watch<PicturesProvider>().allPictures;
    return ListView.builder(
      itemCount: pictures.length,
      cacheExtent: 3,
      itemBuilder: (context, index) 
        return GestureDetector(
          onLongPress: () => Provider.of<PicturesProvider>(context, listen: false).toggleSelectors(),
          onTap: () 
            if (Provider.of<PicturesProvider>(context, listen: false).visible) 
              Provider.of<PicturesProvider>(context, listen: false).toggleSelected(index);
            
          ,
          child: SinglePicture(
            pictureModel: pictures[index],
          ),
        );
      ,
    );
  


最后但并非最不重要的是,应用程序中的 FutureBuilder 调用了所有这些...

body: FutureBuilder(
  future: projFuture,
  // ignore: missing_return
  builder: (BuildContext context, AsyncSnapshot snapshot) 
    if (snapshot.hasData) 
      // Get all picture URLs from project snapshot
      List<dynamic> picUrls = snapshot.data['pictures'].map((pic) => pic['pic_cloud']).toList();
      // Create list of PictureModel objects for Provider
      List<PictureModel> pictures = picUrls
          .map((url) => PictureModel(imageUrl: url, imageHeight: 250.0, selectVisible: false))
          .toList();
      // Add list of PictureModel objects to Provider for UI render
      // context.watch<PicturesProvider>().addPictures(pictures);
      return SafeArea(
        child: PicturesList(pictures),
      );
     else if (snapshot.hasError) 
      print('error');
     else 
      return Center(
        child: CircularProgressIndicator(),
      );
    
  ,
),

很抱歉,后续工作很长,但我想我会尽可能详细地说明如何完成这项工作,以防它对其他人有用。另外,如果有人对如何改进此代码有进一步的建议,请告诉我。

提前致谢。

【讨论】:

以上是关于提供者对象在现有构建期间请求重建的主要内容,如果未能解决你的问题,请参考以下文章

将IndexWriter与SearchManager一起使用

提供者:在构建期间调用的 setState() 或 markNeedsBuild()

如何在WindowClosing事件期间更新JFrame对象?

Azure Service Fabric部署 - 该地区不提供虚拟机来为您的请求提供服务

开始 fetch,在 fetch 期间对中间请求进行排队,然后为所有人提供数据

“在构建期间调用 setState() 或 markNeedsBuild()”错误尝试在消费者小部件(提供程序包)内的导航器中推送替换