wonders/lib/logic/common/retry_image.dart

113 lines
3.6 KiB
Dart
Raw Normal View History

2022-08-29 20:38:28 -06:00
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
/// An image provider that retries if loading the bytes failed.
///
/// Useful for network image requests that may transiently fail.
@immutable
class RetryImage extends ImageProvider<Object> {
/// Creates an object that uses [imageProvider] to fetch and decode an image,
/// and retries if fetching fails.
const RetryImage(this.imageProvider, {this.scale = 1.0, this.maxRetries = 4});
/// A wrapped image provider to use.
final ImageProvider imageProvider;
/// The maximum number of times to retry.
final int maxRetries;
/// The scale to place in the [ImageInfo] object of the image.
///
/// Must be the same as the scale argument provided to [imageProvider], if
/// any.
final double scale;
@override
Future<Object> obtainKey(ImageConfiguration configuration) {
Completer<Object>? completer;
// If the imageProvider.obtainKey future is synchronous, then we will be able to fill in result with
// a value before completer is initialized below.
SynchronousFuture<Object>? result;
imageProvider.obtainKey(configuration).then((Object key) {
if (completer == null) {
// This future has completed synchronously (completer was never assigned),
// so we can directly create the synchronous result to return.
result = SynchronousFuture<Object>(key);
} else {
// This future did not synchronously complete.
completer.complete(key);
}
});
if (result != null) {
return result!;
}
// If the code reaches here, it means the imageProvider.obtainKey was not
// completed sync, so we initialize the completer for completion later.
completer = Completer<Object>();
return completer.future;
}
@override
ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) {
2022-08-29 20:38:28 -06:00
final _DelegatingImageStreamCompleter completer = _DelegatingImageStreamCompleter();
ImageStreamCompleter completerToWrap = imageProvider.loadImage(key, decode);
2022-08-29 20:38:28 -06:00
late ImageStreamListener listener;
Duration duration = const Duration(milliseconds: 250);
int count = 1;
listener = ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
completer.addImage(image);
},
onChunk: completer._reportChunkEvent,
onError: (Object exception, StackTrace? stackTrace) {
completerToWrap.removeListener(listener);
if (count > maxRetries) {
completer.reportError(exception: exception, stack: stackTrace);
return;
}
Future<void>.delayed(duration).then((void v) {
duration *= 2;
completerToWrap = imageProvider.loadImage(key, decode);
2022-08-29 20:38:28 -06:00
count += 1;
completerToWrap.addListener(listener);
});
},
);
completerToWrap.addListener(listener);
completer.addOnLastListenerRemovedCallback(() {
completerToWrap.removeListener(listener);
});
return completer;
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is RetryImage && other.imageProvider == imageProvider && other.scale == scale;
2022-08-29 20:38:28 -06:00
}
@override
int get hashCode => Object.hash(imageProvider, scale);
@override
String toString() =>
'${objectRuntimeType(this, 'RetryImage')}(imageProvider: $imageProvider, maxRetries: $maxRetries, scale: $scale)';
}
class _DelegatingImageStreamCompleter extends ImageStreamCompleter {
void addImage(ImageInfo info) {
setImage(info);
}
void _reportChunkEvent(ImageChunkEvent event) {
reportImageChunkEvent(event);
}
}