如何根据先前下载的信息获取图像列表?
How to fetch a list of images based on previously downloaded information?
我有 2 个休息 API,一个用于数据,另一个用于图像。所以我这样做:
- 获取数据。
- 遍历数据。
- 在每个数据循环中,然后获取图像。
但是发生的是所有加载的图像总是使用列表的最后一个图像。我是这样做的:
//this is the list fetch
return
StreamBuilder(
stream: myBloc.myList,
builder: (context, AsyncSnapshot<List<Result>> snapshot){
if (snapshot.hasData) {
//build the list
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
},
);
Widget buildList(AsyncSnapshot<List<Result>> snapshot) {
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (BuildContext context, int index) {
//fetch the image
myBloc.fetchImageById(snapshot.data[index].Id.toString());
return
StreamBuilder(
stream: myBloc.imageById,
builder: (context, AsyncSnapshot<dynamic> snapshotImg){
if (snapshotImg.hasData) {
return
MyPlaceholder(
imageProvider: snapshotImg.data,
title: snapshot.data[index].name,
);
} else if (snapshotImg.hasError) {
return
MyPlaceholder(
imageProvider: null,
title: snapshot.data[index].name,
);
}
return Center(child: CircularProgressIndicator());
},
);
});
}
这是我的 BLoC class:
class MyBloc {
final _repository = MyRepository();
final _fetcher = PublishSubject<List<Result>>();
final _fetcherImage = PublishSubject<dynamic>();
Observable<List<Result>> get myList => _fetcher.stream;
Observable<dynamic> get myImageById => _fetcherImage.stream;
fetchResultList() async {
List<Result> result = await _repository.fetchMyList();
_fetcher.sink.add(result);
}
fetchImageById(String _id) async {
dynamic imgBinary = await _repository.fetchImageById(_id);
_fetcherImage.sink.add(imgBinary);
}
dispose() {
_fetcher.close();
_fetcherImage.close();
}
}
final categoryBloc = CategoryBloc();
我错过了吗?在另一个 bloc 中不可能有 bloc Observable?
But what happens is all loaded image always the last image of the
list.
您的 BLoC 中的图像只有一个流,因此您的 GridView 行中所有相应的 StreamBuilder 将仅更新快照中的最后一个值(并且您最终得到最后一个图像)。
如果您确定只有几张图片要在您的 GridView 中显示,您可以使 fetchImageById( ) 方法为该特定图像创建流,保留对它的引用,然后 return 它。然后,您可以将 returned 流传递给行 StreamBuilder 而不是 myBloc.imageById,这样您的行 StreamBuilder 将具有不同的数据源。加载图像后,您可以将其添加到该特定流(基于 id),并且您的行将仅使用该特定数据进行更新。部分代码:
//fetch the image
Observable<dynamic> imageStream = myBloc.fetchImageById(snapshot.data[index].Id.toString());
return StreamBuilder(
stream: imageStream,
builder: (context, AsyncSnapshot<dynamic> snapshotImg){
// rest of code
在您的 BLoC 中,您将拥有:
Map<String, PublishSubject<dynamic>> _backingImageStreams = HashMap()
Observable<dynamic> fetchImageById(String _id) {
PublishSubject<dynamic> backingImgStream = _backingImageStreams[id];
if (backingImgStream == null) {
backingImgStream = PublishSubject<dynamic>();
_backingImageStreams[id] = backingImgStream;
}
// i'm assuming that repository.fetchImageById() returns a Future ?!
_repository.fetchImageById(_id).then((){
_fetcherImage.sink.add(imgBinary);
});
return _fetcherImage.stream;
}
在更一般的情况下,我认为您需要将代码从 StreamBuilder 更改为 FutureBuilder。在您的小部件中,您将拥有:
Widget buildList(AsyncSnapshot<List<Result>> snapshot) {
// ...
itemBuilder: (BuildContext context, int index) {
//fetch the image
Future<dynamic> imageFuture = myBloc.fetchImageById(snapshot.data[index].Id.toString());
return FutureBuilder(
future: imageFuture,
builder: (context, AsyncSnapshot<dynamic> snapshotImg){
// rest of your current code
然后您需要更改 BLoC 方法 fetchImageById()。当你处理图像时,你会想要实现某种缓存,这样你会更有效率:
- 如果您已经拥有相同的图像,则不要再次下载它(并快速向用户展示)
- 不要一次加载所有图像并使内存混乱(或完全失败)
BLoC代码:
class MyBloc {
// remove the imageId observable
// A primitive and silly cache. This will only make sure we don't make extra
// requests for images if we already have the image data, BUT if the user
// scrolls the entire GridView we will also have in memory all the image data.
// This should be replaced with some sort of disk based cache or something
// that limits the amount of memory the cache uses.
final Map<String, dynamic> cache = HashMap();
FutureOr<dynamic> fetchImageById(String _id) async {
// atempt to find the image in the cache, maybe we already downloaded it
dynamic image = cache[id];
// if we found an image for this id then we can simply return it
if (image != null) {
return image;
} else {
// this is the first time we fetch the image, or the image was previously disposed from the cache and we need to get it
dynamic image = // your code to fetch the image
// put the image in the cache so we have it for future requests
cache[id] = image;
// return the downloaded image, you could also return the Future of the fetch request but you need to add it to the cache
return image;
}
}
如果您只想向用户显示图像,只需将 fetchImageById() 设置为 return 图像提取请求的未来(但每次小部件都将发出提取请求建成)。
我有 2 个休息 API,一个用于数据,另一个用于图像。所以我这样做:
- 获取数据。
- 遍历数据。
- 在每个数据循环中,然后获取图像。
但是发生的是所有加载的图像总是使用列表的最后一个图像。我是这样做的:
//this is the list fetch
return
StreamBuilder(
stream: myBloc.myList,
builder: (context, AsyncSnapshot<List<Result>> snapshot){
if (snapshot.hasData) {
//build the list
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
},
);
Widget buildList(AsyncSnapshot<List<Result>> snapshot) {
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (BuildContext context, int index) {
//fetch the image
myBloc.fetchImageById(snapshot.data[index].Id.toString());
return
StreamBuilder(
stream: myBloc.imageById,
builder: (context, AsyncSnapshot<dynamic> snapshotImg){
if (snapshotImg.hasData) {
return
MyPlaceholder(
imageProvider: snapshotImg.data,
title: snapshot.data[index].name,
);
} else if (snapshotImg.hasError) {
return
MyPlaceholder(
imageProvider: null,
title: snapshot.data[index].name,
);
}
return Center(child: CircularProgressIndicator());
},
);
});
}
这是我的 BLoC class:
class MyBloc {
final _repository = MyRepository();
final _fetcher = PublishSubject<List<Result>>();
final _fetcherImage = PublishSubject<dynamic>();
Observable<List<Result>> get myList => _fetcher.stream;
Observable<dynamic> get myImageById => _fetcherImage.stream;
fetchResultList() async {
List<Result> result = await _repository.fetchMyList();
_fetcher.sink.add(result);
}
fetchImageById(String _id) async {
dynamic imgBinary = await _repository.fetchImageById(_id);
_fetcherImage.sink.add(imgBinary);
}
dispose() {
_fetcher.close();
_fetcherImage.close();
}
}
final categoryBloc = CategoryBloc();
我错过了吗?在另一个 bloc 中不可能有 bloc Observable?
But what happens is all loaded image always the last image of the list.
您的 BLoC 中的图像只有一个流,因此您的 GridView 行中所有相应的 StreamBuilder 将仅更新快照中的最后一个值(并且您最终得到最后一个图像)。
如果您确定只有几张图片要在您的 GridView 中显示,您可以使 fetchImageById( ) 方法为该特定图像创建流,保留对它的引用,然后 return 它。然后,您可以将 returned 流传递给行 StreamBuilder 而不是 myBloc.imageById,这样您的行 StreamBuilder 将具有不同的数据源。加载图像后,您可以将其添加到该特定流(基于 id),并且您的行将仅使用该特定数据进行更新。部分代码:
//fetch the image
Observable<dynamic> imageStream = myBloc.fetchImageById(snapshot.data[index].Id.toString());
return StreamBuilder(
stream: imageStream,
builder: (context, AsyncSnapshot<dynamic> snapshotImg){
// rest of code
在您的 BLoC 中,您将拥有:
Map<String, PublishSubject<dynamic>> _backingImageStreams = HashMap()
Observable<dynamic> fetchImageById(String _id) {
PublishSubject<dynamic> backingImgStream = _backingImageStreams[id];
if (backingImgStream == null) {
backingImgStream = PublishSubject<dynamic>();
_backingImageStreams[id] = backingImgStream;
}
// i'm assuming that repository.fetchImageById() returns a Future ?!
_repository.fetchImageById(_id).then((){
_fetcherImage.sink.add(imgBinary);
});
return _fetcherImage.stream;
}
在更一般的情况下,我认为您需要将代码从 StreamBuilder 更改为 FutureBuilder。在您的小部件中,您将拥有:
Widget buildList(AsyncSnapshot<List<Result>> snapshot) {
// ...
itemBuilder: (BuildContext context, int index) {
//fetch the image
Future<dynamic> imageFuture = myBloc.fetchImageById(snapshot.data[index].Id.toString());
return FutureBuilder(
future: imageFuture,
builder: (context, AsyncSnapshot<dynamic> snapshotImg){
// rest of your current code
然后您需要更改 BLoC 方法 fetchImageById()。当你处理图像时,你会想要实现某种缓存,这样你会更有效率:
- 如果您已经拥有相同的图像,则不要再次下载它(并快速向用户展示)
- 不要一次加载所有图像并使内存混乱(或完全失败)
BLoC代码:
class MyBloc {
// remove the imageId observable
// A primitive and silly cache. This will only make sure we don't make extra
// requests for images if we already have the image data, BUT if the user
// scrolls the entire GridView we will also have in memory all the image data.
// This should be replaced with some sort of disk based cache or something
// that limits the amount of memory the cache uses.
final Map<String, dynamic> cache = HashMap();
FutureOr<dynamic> fetchImageById(String _id) async {
// atempt to find the image in the cache, maybe we already downloaded it
dynamic image = cache[id];
// if we found an image for this id then we can simply return it
if (image != null) {
return image;
} else {
// this is the first time we fetch the image, or the image was previously disposed from the cache and we need to get it
dynamic image = // your code to fetch the image
// put the image in the cache so we have it for future requests
cache[id] = image;
// return the downloaded image, you could also return the Future of the fetch request but you need to add it to the cache
return image;
}
}
如果您只想向用户显示图像,只需将 fetchImageById() 设置为 return 图像提取请求的未来(但每次小部件都将发出提取请求建成)。