Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_map_example/pages/animated_map_controller.dart';
import 'package:flutter_map_example/pages/cancellable_tile_provider/cancellable_tile_provider.dart';
import 'package:flutter_map_example/pages/circle.dart';
import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart';
import 'package:flutter_map_example/pages/epsg3413_crs.dart';
Expand Down Expand Up @@ -47,6 +48,8 @@ class MyApp extends StatelessWidget {
),
home: const HomePage(),
routes: <String, WidgetBuilder>{
CancellableTileProviderPage.route: (context) =>
const CancellableTileProviderPage(),
PolylinePage.route: (context) => const PolylinePage(),
MapControllerPage.route: (context) => const MapControllerPage(),
AnimatedMapControllerPage.route: (context) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter_map_example/pages/cancellable_tile_provider/ctp_impl.dart';
import 'package:flutter_map_example/widgets/drawer.dart';
import 'package:latlong2/latlong.dart';

class CancellableTileProviderPage extends StatelessWidget {
static const String route = '/cancellable_tile_provider_page';

const CancellableTileProviderPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cancellable Tile Provider')),
drawer: buildDrawer(context, CancellableTileProviderPage.route),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(12),
child: Text(
'This map uses a custom `TileProvider` that cancels HTTP requests for unnecessary tiles. This should help speed up tile loading and reduce unneccessary costly tile requests, mainly on the web!',
),
),
Expanded(
child: FlutterMap(
options: MapOptions(
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 5,
cameraConstraint: CameraConstraint.contain(
bounds: LatLngBounds(
const LatLng(-90, -180),
const LatLng(90, 180),
),
),
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
tileProvider: CancellableNetworkTileProvider(),
),
],
),
),
],
),
);
}
}
115 changes: 115 additions & 0 deletions example/lib/pages/cancellable_tile_provider/ctp_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'dart:async';
import 'dart:ui';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:http/http.dart';
import 'package:http/retry.dart';

class CancellableNetworkTileProvider extends TileProvider {
CancellableNetworkTileProvider({
super.headers,
BaseClient? httpClient,
}) : httpClient = httpClient ?? RetryClient(Client());

final BaseClient httpClient;

@override
bool get supportsCancelLoading => true;

@override
ImageProvider getImageWithCancelLoadingSupport(
TileCoordinates coordinates,
TileLayer options,
Future<void> cancelLoading,
) =>
CancellableNetworkImageProvider(
url: getTileUrl(coordinates, options),
fallbackUrl: getTileFallbackUrl(coordinates, options),
headers: headers,
httpClient: httpClient,
cancelLoading: cancelLoading,
);
}

class CancellableNetworkImageProvider
extends ImageProvider<CancellableNetworkImageProvider> {
final String url;
final String? fallbackUrl;
final BaseClient httpClient;
final Map<String, String> headers;
final Future<void> cancelLoading;

const CancellableNetworkImageProvider({
required this.url,
required this.fallbackUrl,
required this.headers,
required this.httpClient,
required this.cancelLoading,
});

@override
ImageStreamCompleter loadImage(
CancellableNetworkImageProvider key,
ImageDecoderCallback decode,
) {
final chunkEvents = StreamController<ImageChunkEvent>();

return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: 1,
debugLabel: url,
informationCollector: () => [
DiagnosticsProperty('URL', url),
DiagnosticsProperty('Fallback URL', fallbackUrl),
DiagnosticsProperty('Current provider', key),
],
);
}

@override
Future<CancellableNetworkImageProvider> obtainKey(
ImageConfiguration configuration,
) =>
SynchronousFuture<CancellableNetworkImageProvider>(this);

Future<Codec> _loadAsync(
CancellableNetworkImageProvider key,
StreamController<ImageChunkEvent> chunkEvents,
ImageDecoderCallback decode, {
bool useFallback = false,
}) async {
final cancelToken = CancelToken();
cancelLoading.then((_) => cancelToken.cancel());

final Uint8List bytes;
try {
final dio = Dio();
final response = await dio.get<Uint8List>(
useFallback ? fallbackUrl ?? '' : url,
cancelToken: cancelToken,
options: Options(
headers: headers,
responseType: ResponseType.bytes,
),
);
bytes = response.data!;
} on DioException catch (err) {
if (CancelToken.isCancel(err)) {
return decode(
await ImmutableBuffer.fromUint8List(TileProvider.transparentImage),
);
}
if (useFallback || fallbackUrl == null) rethrow;
return _loadAsync(key, chunkEvents, decode, useFallback: true);
} catch (_) {
if (useFallback || fallbackUrl == null) rethrow;
return _loadAsync(key, chunkEvents, decode, useFallback: true);
}

return decode(await ImmutableBuffer.fromUint8List(bytes));
}
}
7 changes: 7 additions & 0 deletions example/lib/widgets/drawer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'package:flutter_map_example/pages/animated_map_controller.dart';
import 'package:flutter_map_example/pages/cancellable_tile_provider/cancellable_tile_provider.dart';
import 'package:flutter_map_example/pages/circle.dart';
import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart';
import 'package:flutter_map_example/pages/epsg3413_crs.dart';
Expand Down Expand Up @@ -153,6 +154,12 @@ Drawer buildDrawer(BuildContext context, String currentRoute) {
FallbackUrlNetworkPage.route,
currentRoute,
),
_buildMenuItem(
context,
const Text('Cancellable Tile Provider'),
CancellableTileProviderPage.route,
currentRoute,
),
const Divider(),
_buildMenuItem(
context,
Expand Down
2 changes: 2 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies:
url_launcher: ^6.1.10
shared_preferences: ^2.1.1
url_strategy: ^0.2.0
http: ^1.1.0
dio: ^5.3.2

dev_dependencies:
flutter_lints: ^2.0.1
Expand Down
36 changes: 36 additions & 0 deletions lib/src/layer/tile_layer/tile_error_evict_callback.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
part of 'tile_layer.dart';

@Deprecated(
'Prefer creating a custom `TileProvider` instead. '
'This option has been deprecated as it is out of scope for the `TileLayer`. '
'This option is deprecated since v6.',
)
typedef TemplateFunction = String Function(
String str,
Map<String, String> data,
);

enum EvictErrorTileStrategy {
/// Never evict images for tiles which failed to load.
none,

/// Evict images for tiles which failed to load when they are pruned.
dispose,

/// Evict images for tiles which failed to load and:
/// - do not belong to the current zoom level AND/OR
/// - are not visible, respecting the pruning buffer (the maximum of the
/// [keepBuffer] and [panBuffer].
notVisibleRespectMargin,

/// Evict images for tiles which failed to load and:
/// - do not belong to the current zoom level AND/OR
/// - are not visible
notVisible,
}

typedef ErrorTileCallBack = void Function(
TileImage tile,
Object error,
StackTrace? stackTrace,
);
12 changes: 12 additions & 0 deletions lib/src/layer/tile_layer/tile_image.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_display.dart';
Expand Down Expand Up @@ -35,6 +37,11 @@ class TileImage extends ChangeNotifier {
/// An optional image to show when a loading error occurs.
final ImageProvider? errorImage;

/// Completer that is completed when this object is disposed
///
/// Intended to allow [TileProvider]s to cancel unneccessary HTTP requests.
final Completer<void> cancelLoading;

ImageProvider imageProvider;

/// True if an error occurred during loading.
Expand All @@ -58,6 +65,7 @@ class TileImage extends ChangeNotifier {
required this.onLoadError,
required TileDisplay tileDisplay,
required this.errorImage,
required this.cancelLoading,
}) : _tileDisplay = tileDisplay,
_animationController = tileDisplay.when(
instantaneous: (_) => null,
Expand Down Expand Up @@ -126,6 +134,8 @@ class TileImage extends ChangeNotifier {

/// Initiate loading of the image.
void load() {
if (cancelLoading.isCompleted) return;

loadStarted = DateTime.now();

try {
Expand Down Expand Up @@ -230,6 +240,8 @@ class TileImage extends ChangeNotifier {
}
}

cancelLoading.complete();

_readyToDisplay = false;
_animationController?.stop(canceled: false);
_animationController?.value = 0.0;
Expand Down
14 changes: 10 additions & 4 deletions lib/src/layer/tile_layer/tile_image_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,16 @@ class TileImageManager {
final tilesToReload = List<TileImage>.from(_tiles.values);

for (final tile in tilesToReload) {
tile.imageProvider = layer.tileProvider.getImage(
tileBounds.atZoom(tile.coordinates.z).wrap(tile.coordinates),
layer,
);
tile.imageProvider = layer.tileProvider.supportsCancelLoading
? layer.tileProvider.getImageWithCancelLoadingSupport(
tileBounds.atZoom(tile.coordinates.z).wrap(tile.coordinates),
layer,
tile.cancelLoading.future,
)
: layer.tileProvider.getImage(
tileBounds.atZoom(tile.coordinates.z).wrap(tile.coordinates),
layer,
);
Comment thread
JaffaKetchup marked this conversation as resolved.
tile.load();
}
}
Expand Down
Loading