提供者对象请求在现有构建期间重建
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(),
);
}
},
),
很抱歉长时间跟进,但我想我会尽可能详细地说明如何进行这项工作,以防它对其他人有用。另外,如果有人对如何改进此代码有进一步的建议,请告诉我。
提前致谢。
我正在学习 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(),
);
}
},
),
很抱歉长时间跟进,但我想我会尽可能详细地说明如何进行这项工作,以防它对其他人有用。另外,如果有人对如何改进此代码有进一步的建议,请告诉我。
提前致谢。