Skip to content

Commit

Permalink
Fixed issues
Browse files Browse the repository at this point in the history
Added custom `ImageProvider`s
Reduced reliance on 'universal_io' library
Prepared for review
  • Loading branch information
JaffaKetchup committed Jul 9, 2022
1 parent d7c7648 commit ad2adb6
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 91 deletions.
10 changes: 7 additions & 3 deletions lib/src/layer/tile_layer/tile_layer_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,14 @@ class TileLayerOptions extends LayerOptions {
: Map.from(additionalOptions),
tileProvider = tileProvider == null
? NetworkNoRetryTileProvider(
headers: {'User-Agent': 'flutter_map ($userAgentPackageName)'})
headers: {'User-Agent': 'flutter_map ($userAgentPackageName)'},
)
: (tileProvider
..headers.putIfAbsent(
'User-Agent', () => 'flutter_map ($userAgentPackageName)')),
..headers = <String, String>{
...tileProvider.headers,
if (!tileProvider.headers.containsKey('User-Agent'))
'User-Agent': 'flutter_map ($userAgentPackageName)',
}),
super(key: key, rebuild: rebuild);
}

Expand Down
55 changes: 55 additions & 0 deletions lib/src/layer/tile_layer/tile_provider/network_image_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:http/http.dart';
import 'package:http/retry.dart';

class NetworkImageProvider extends ImageProvider<NetworkImageProvider> {
/// The URL from which the image will be fetched.
final String url;

/// The http RetryClient that is used for the requests
final RetryClient retryClient;

/// Custom headers to add to the image fetch request
final Map<String, String> headers;

NetworkImageProvider(
this.url, {
RetryClient? retryClient,
this.headers = const {},
}) : retryClient = retryClient ?? RetryClient(Client());

@override
ImageStreamCompleter load(NetworkImageProvider key, DecoderCallback decode) {
return OneFrameImageStreamCompleter(_loadWithRetry(key, decode),
informationCollector: () sync* {
yield ErrorDescription('Image provider: $this');
yield ErrorDescription('Image key: $key');
});
}

@override
Future<NetworkImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<NetworkImageProvider>(this);
}

Future<ImageInfo> _loadWithRetry(
NetworkImageProvider key,
DecoderCallback decode,
) async {
assert(key == this);

final uri = Uri.parse(url);
final response = await retryClient.get(uri, headers: headers);

if (response.statusCode != 200) {
throw NetworkImageLoadException(
statusCode: response.statusCode, uri: uri);
}

final codec = await decode(response.bodyBytes);
final image = (await codec.getNextFrame()).image;

return ImageInfo(image: image);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';

class NetworkNoRetryImageProvider
extends ImageProvider<NetworkNoRetryImageProvider> {
/// A valid URL, which is the location of the image to be fetched
final String url;

/// The client which will be used to fetch the image
final HttpClient httpClient;

/// Custom headers to add to the image fetch request
final Map<String, String> headers;

NetworkNoRetryImageProvider(
this.url, {
HttpClient? httpClient,
this.headers = const {},
}) : httpClient = httpClient ?? HttpClient()
..userAgent = null;

@override
ImageStreamCompleter load(
NetworkNoRetryImageProvider key,
DecoderCallback decode,
) {
final StreamController<ImageChunkEvent> chunkEvents =
StreamController<ImageChunkEvent>();

return MultiFrameImageStreamCompleter(
codec: _loadAsync(key: key, decode: decode, chunkEvents: chunkEvents),
chunkEvents: chunkEvents.stream,
scale: 1,
debugLabel: key.url,
informationCollector: () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<NetworkNoRetryImageProvider>('Image key', key),
],
);
}

@override
Future<NetworkNoRetryImageProvider> obtainKey(
ImageConfiguration configuration) {
return SynchronousFuture<NetworkNoRetryImageProvider>(this);
}

Future<Codec> _loadAsync({
required NetworkNoRetryImageProvider key,
required DecoderCallback decode,
required StreamController<ImageChunkEvent> chunkEvents,
}) async {
try {
assert(key == this);

final Uri resolved = Uri.base.resolve(key.url);

final HttpClientRequest request = await httpClient.getUrl(resolved);

headers.forEach((String name, String value) {
request.headers.add(name, value);
});

final HttpClientResponse response = await request.close();
if (response.statusCode != HttpStatus.ok) {
await response.drain<List<int>>(<int>[]);
throw NetworkImageLoadException(
statusCode: response.statusCode, uri: resolved);
}

final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (int cumulative, int? total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
));
},
);
if (bytes.lengthInBytes == 0) {
throw Exception('NetworkImage is an empty file: $resolved');
}

return decode(bytes);
} catch (e) {
scheduleMicrotask(() {
PaintingBinding.instance.imageCache.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}
}
88 changes: 46 additions & 42 deletions lib/src/layer/tile_layer/tile_provider/tile_provider.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
// ignore_for_file: avoid_print
// TODO: Remove print statements

import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:http/http.dart';
import 'package:http/retry.dart';
import 'package:universal_io/io.dart';

import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_image_with_retry.dart';

const Map<String, String> _defaultHeader = {
'User-Agent': 'flutter_map via Dart (unknown)',
};
import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_image_provider.dart';

abstract class TileProvider {
Map<String, String> headers;

TileProvider({
this.headers = _defaultHeader,
this.headers = const {},
});

ImageProvider getImage(Coords coords, TileLayerOptions options);
Expand Down Expand Up @@ -70,55 +64,57 @@ abstract class TileProvider {
}
}

/// [TileProvider] that uses [NetworkImageWithRetry] internally
/// [TileProvider] that uses [NetworkImageProvider] internally
///
/// Note that this is not recommended, as there is no way to set headers with this method: see https://github.com/flutter/flutter/issues/19532.
/// The parameter is only provided for potential forward-compatibility.
/// This image provider automatically retries some failed requests up to 3 times.
///
/// TODO: Add header capabilities through `HttpOverrides` or (preferably) by changing the image provider's implementation
/// Note that this provider may be slower than [NetworkNoRetryTileProvider] when fetching tiles due to internal reasons.
class NetworkTileProvider extends TileProvider {
NetworkTileProvider({
Map<String, String>? headers,
RetryClient? retryClient,
}) {
this.headers = headers ?? _defaultHeader;
this.headers = headers ?? {};
this.retryClient = retryClient ?? RetryClient(Client());
}

late final RetryClient retryClient;

@override
ImageProvider getImage(Coords<num> coords, TileLayerOptions options) {
return NetworkImageWithRetry(getTileUrl(coords, options));
return HttpOverrides.runZoned<NetworkImageProvider>(
() => NetworkImageProvider(
getTileUrl(coords, options),
headers: headers,
retryClient: retryClient,
),
createHttpClient: (c) => _FlutterMapHTTPOverrides().createHttpClient(c),
);
}
}

/// [TileProvider] that uses [NetworkImage] internally
/// [TileProvider] that uses [NetworkNoRetryImageProvider] internally
///
/// This image provider does not automatically retry any failed requests. This provider is the default and the recommended provider, unless your tile server is especially unreliable.
class NetworkNoRetryTileProvider extends TileProvider {
NetworkNoRetryTileProvider({
Map<String, String>? headers,
HttpClient? httpClient,
}) {
this.headers = headers ?? _defaultHeader;
//HttpOverrides.global = _FlutterMapHTTPOverrides();
this.headers = headers ?? {};
this.httpClient = httpClient ?? HttpClient()
..userAgent = null;
}

late final HttpClient httpClient;

@override
ImageProvider getImage(Coords<num> coords, TileLayerOptions options) {
print('Header: ${headers['User-Agent']}');
print("Running in ${Zone.current.toString()}");
return HttpOverrides.runZoned<NetworkImage>(
() {
/*print("Running in ${Zone.current}");*/
final HttpClient httpClient = HttpClient();
print("userAgent = ${httpClient.userAgent}");

return NetworkImage(
getTileUrl(coords, options),
headers: headers,
);
},
createHttpClient: (c) {
print('Is creating HTTP client for zone');
return _FlutterMapHTTPOverrides().createHttpClient(c);
},
);
}
ImageProvider getImage(Coords<num> coords, TileLayerOptions options) =>
NetworkNoRetryImageProvider(
getTileUrl(coords, options),
headers: headers,
httpClient: httpClient,
);
}

/// Deprecated due to internal refactoring. The name is misleading, as the internal [ImageProvider] always caches, and this is recommended by most tile servers anyway. For the same functionality, migrate to [NetworkNoRetryTileProvider] before the next minor update.
Expand All @@ -127,13 +123,21 @@ class NetworkNoRetryTileProvider extends TileProvider {
class NonCachingNetworkTileProvider extends TileProvider {
NonCachingNetworkTileProvider({
Map<String, String>? headers,
HttpClient? httpClient,
}) {
this.headers = headers ?? _defaultHeader;
this.headers = headers ?? {};
this.httpClient = httpClient ?? HttpClient()
..userAgent = null;
}

late final HttpClient httpClient;

@override
ImageProvider getImage(Coords<num> coords, TileLayerOptions options) =>
NetworkNoRetryTileProvider(headers: headers).getImage(coords, options);
NetworkNoRetryTileProvider(
headers: headers,
httpClient: httpClient,
).getImage(coords, options);
}

class AssetTileProvider extends TileProvider {
Expand Down

0 comments on commit ad2adb6

Please sign in to comment.