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

Provider Object Requests Rebuild During Existing Build

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

我的代码有效,但它在每个 LongPress 上抛出异常,指出“setState() 或 markNeedsBuild() 在构建 期间被调用,”我正在抓头发试图弄清楚如何延迟 ChangeNotifier 直到构建小部件树?或者其他一些方法来完成这个任务?

我的提供商 class 只接受我的 PictureModel class 的列表,并且有一个 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 class 将网络图像加载到 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 class 中调用此 SinglePicture class构建一个 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(),
      );
    }
  },
),

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

感谢 Remi Rousselet 的回答:

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

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

这里是使用 LongPress 制作我的图像 'selectable' 并能够通过点击单独 select 它们的代码片段,如下图所示:

我的图片型号:

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();
  }
}

我的单张图片UIclass:

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,
                      ),
          )
        ],
      ),
    );
  }
}

我的图片列表UIclass:

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(),
      );
    }
  },
),

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

提前致谢。