Skip to content

Instantly share code, notes, and snippets.

@incon
Forked from ueman/dio_image.dart
Created January 5, 2023 03:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save incon/36e26bfeae915599897239cc5c8be27f to your computer and use it in GitHub Desktop.
Save incon/36e26bfeae915599897239cc5c8be27f to your computer and use it in GitHub Desktop.
http and dio image
/// Requires at least Flutter 3.x
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
/// Fetches the given URL from the network, associating it with the given scale.
///
/// The image will be cached regardless of cache headers from the server.
///
/// See also:
///
/// * [Image.network].
@immutable
class DioImage extends ImageProvider<DioImage> {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
/// [dio] will be the default [Dio] if not set.
DioImage(this.url, {this.scale = 1.0, this.headers, Dio? dio})
: dio = dio ?? Dio();
/// The URL from which the image will be fetched.
final Uri url;
/// The scale to place in the [ImageInfo] object of the image.
final double scale;
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
///
/// When running flutter on the web, headers are not used.
final Map<String, String>? headers;
/// [dio] will be the default [Dio] if not set.
final Dio dio;
@override
Future<DioImage> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<DioImage>(this);
}
@override
ImageStreamCompleter loadBuffer(DioImage key, DecoderBufferCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
debugLabel: key.url.toString(),
informationCollector: () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<DioImage>('Image key', key),
],
);
}
Future<ui.Codec> _loadAsync(
DioImage key,
StreamController<ImageChunkEvent> chunkEvents,
DecoderBufferCallback decode,
) async {
try {
assert(key == this);
final response = await dio.getUri(
url,
options: Options(headers: headers, responseType: ResponseType.bytes),
onReceiveProgress: (count, total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: count,
expectedTotalBytes: total,
));
},
);
if (response.statusCode != 200) {
throw NetworkImageLoadException(
statusCode: response.statusCode!,
uri: url,
);
}
final bytes = Uint8List.fromList(response.data as List<int>);
if (bytes.lengthInBytes == 0) {
throw Exception('NetworkImage is an empty file: $url');
}
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
} catch (e) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance.imageCache.evict(key);
});
rethrow;
} finally {
unawaited(chunkEvents.close());
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is DioImage && other.url == url && other.scale == scale;
}
@override
int get hashCode => Object.hash(url, scale);
@override
String toString() =>
'${objectRuntimeType(this, 'NetworkImage')}("$url", scale: $scale)';
}
/// Requires at least Flutter 3.x
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart';
/// Fetches the given URL from the network, associating it with the given scale.
///
/// The image will be cached regardless of cache headers from the server.
///
/// See also:
///
/// * [Image.network].
@immutable
class HttpImage extends ImageProvider<HttpImage> {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
/// [client] will be the default [Client] if not set.
HttpImage(this.url, {this.scale = 1.0, this.headers, Client? client})
: client = client ?? Client();
/// The URL from which the image will be fetched.
final Uri url;
/// The scale to place in the [ImageInfo] object of the image.
final double scale;
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
///
/// When running flutter on the web, headers are not used.
final Map<String, String>? headers;
/// [client] will be the default [Client] if not set.
final Client client;
@override
Future<HttpImage> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<HttpImage>(this);
}
@override
ImageStreamCompleter loadBuffer(HttpImage key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as HttpImage, decode),
scale: key.scale,
debugLabel: key.url.toString(),
informationCollector: () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<HttpImage>('Image key', key),
],
);
}
Future<ui.Codec> _loadAsync(
HttpImage key,
DecoderBufferCallback decode,
) async {
try {
assert(key == this);
final response = await client.get(url, headers: headers);
if (response.statusCode != 200) {
throw NetworkImageLoadException(
statusCode: response.statusCode, uri: url);
}
final bytes = response.bodyBytes;
if (bytes.lengthInBytes == 0) {
throw Exception('HttpImage is an empty file: $url');
}
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
} catch (e) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance.imageCache.evict(key);
});
rethrow;
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is HttpImage && other.url == url && other.scale == scale;
}
@override
int get hashCode => Object.hash(url, scale);
@override
String toString() =>
'${objectRuntimeType(this, 'HttpImage')}("$url", scale: $scale)';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment