如何根据先前下载的信息获取图像列表?

How to fetch a list of images based on previously downloaded information?

我有 2 个休息 API,一个用于数据,另一个用于图像。所以我这样做:

  1. 获取数据。
  2. 遍历数据。
  3. 在每个数据循环中,然后获取图像。

但是发生的是所有加载的图像总是使用列表的最后一个图像。我是这样做的:

//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 图像提取请求的未来(但每次小部件都将发出提取请求建成)。