提供者对象在现有构建期间请求重建
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()”错误尝试在消费者小部件(提供程序包)内的导航器中推送替换