How to display a Future<File> image?

Viewed 744

My app load image from samba (instead of url) and then save thumbnail in local, so I can not use Image.network();, previouly I loaded image Uint8List from samba and then use Image.memory(bytes); to display image, it work, but I also have to combine it with Local Cache, so I save image file with flutter_cache_manager package DefaultCacheManager()(using (await DefaultCacheManager().getFileFromCache()).file), but the problem is, the DefaultCacheManager() only can return Future<File> cache instead of File, so I cannot use Image.file(cacheFile) to display image.

I have to try the FutureBuilder to solve the problem, but it cause flash in the image displaying.

I have considered to use FadeInImage and make my own ImageProvider to feed my need, but it is difficult for me to write ImageProvider.

In conclusion, I want to make something like:

Image.futureFile(Future<File>)

to use the Future cache return by DefaultCacheManager() to display a local image.If it cannot be solve(for example, a local cache file api should not return Future), I will conside to use another cache library.

2 Answers

Finally I end up with a custom ImageProvider, it refers to FileImage, I don't master at this so it may have problem (for example I don't test it with gif). After all, now image flashing is better than the FutureBuilder solution.

class CacheImageProvider extends ImageProvider<CacheImageProvider> {
  final String fileId;//the cache id use to get cache

  CacheImageProvider(this.fileId);

  @override
  ImageStreamCompleter load(CacheImageProvider key, DecoderCallback decode) {
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(decode),
      scale: 1.0,
      debugLabel: fileId,
      informationCollector: () sync* {
        yield ErrorDescription('Path: $fileId');
      },
    );
  }

  Future<Codec> _loadAsync(DecoderCallback decode) async {
    // the DefaultCacheManager() encapsulation, it get cache from local storage.
    final Uint8List bytes = await (await CacheThumbnail.getThumbnail(fileId)).readAsBytes();

    if (bytes.lengthInBytes == 0) {
      // The file may become available later.
      PaintingBinding.instance?.imageCache?.evict(this);
      throw StateError('$fileId is empty and cannot be loaded as an image.');
    }

    return await decode(bytes);
  }

  @override
  Future<CacheImageProvider> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture<CacheImageProvider>(this);
  }
  
  //the custom == and hashCode is need, because the ImageProvider use to get the memory cache.
  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) return false;
    bool res = other is CacheImageProvider && other.fileId == fileId;
    return res;
  }

  @override
  int get hashCode => fileId.hashCode;

  @override
  String toString() => '${objectRuntimeType(this, 'CacheImageProvider')}("$fileId")';
}

and use it:

Image(image: CacheImageProvider(_data[index].fileId))
// This should be in a callback or outside the build() function:
File _file = await DefaultCacheManager().getSingleFile(file);

// In the widget tree:
Image.file(_file);
Related