Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cancellation support to TileProvider and surrounding mechanisms #1622

Merged
merged 4 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
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,
);
JaffaKetchup marked this conversation as resolved.
Show resolved Hide resolved
tile.load();
}
}
Expand Down
78 changes: 45 additions & 33 deletions lib/src/layer/tile_layer/tile_layer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:math' as math hide Point;
import 'dart:math' show Point;
import 'dart:math';

import 'package:collection/collection.dart' show MapEquality;
import 'package:flutter/material.dart';
Expand All @@ -12,10 +11,10 @@ import 'package:flutter_map/src/layer/tile_layer/tile_image_manager.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_range.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_range_calculator.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_scale_calculator.dart';
import 'package:flutter_map/src/misc/private/util.dart' as util;
import 'package:http/retry.dart';

part 'tile_layer_options.dart';
part 'tile_error_evict_callback.dart';
part 'wms_tile_layer_options.dart';

/// Describes the needed properties to create a tile-based layer. A tile is an
/// image bound to a specific geographical position.
Expand Down Expand Up @@ -195,7 +194,12 @@ class TileLayer extends StatefulWidget {
/// This callback will be executed if an error occurs when fetching tiles.
final ErrorTileCallBack? errorTileCallback;

final TemplateFunction templateFunction;
@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.',
)
final TemplateFunction? templateFunction;

/// Function which may Wrap Tile with custom Widget
/// There are predefined examples in 'tile_builder.dart'
Expand Down Expand Up @@ -234,7 +238,7 @@ class TileLayer extends StatefulWidget {
this.maxNativeZoom,
this.zoomReverse = false,
double zoomOffset = 0.0,
Map<String, String>? additionalOptions,
this.additionalOptions = const {},
this.subdomains = const <String>[],
this.keepBuffer = 2,
this.panBuffer = 0,
Expand All @@ -246,45 +250,42 @@ class TileLayer extends StatefulWidget {
this.tileDisplay = const TileDisplay.fadeIn(),
this.retinaMode = false,
this.errorTileCallback,
this.templateFunction = util.template,
@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.',
)
this.templateFunction,
this.tileBuilder,
this.evictErrorTileStrategy = EvictErrorTileStrategy.none,
this.reset,
this.tileBounds,
TileUpdateTransformer? tileUpdateTransformer,
String userAgentPackageName = 'unknown',
}) : assert(
tileDisplay.when(
instantaneous: (_) => true,
fadeIn: (fadeIn) => fadeIn.duration > Duration.zero)!,
'The tile fade in duration needs to be bigger than zero'),
tileDisplay.when(
instantaneous: (_) => true,
fadeIn: (fadeIn) => fadeIn.duration > Duration.zero,
)!,
'The tile fade in duration needs to be bigger than zero',
),
maxZoom =
wmsOptions == null && retinaMode && maxZoom > 0.0 && !zoomReverse
? maxZoom - 1.0
: maxZoom,
minZoom =
wmsOptions == null && retinaMode && maxZoom > 0.0 && zoomReverse
? math.max(minZoom + 1.0, 0)
? max(minZoom + 1.0, 0)
: minZoom,
zoomOffset = wmsOptions == null && retinaMode && maxZoom > 0.0
? (zoomReverse ? zoomOffset - 1.0 : zoomOffset + 1.0)
: zoomOffset,
tileSize = wmsOptions == null && retinaMode && maxZoom > 0.0
? (tileSize / 2.0).floorToDouble()
: tileSize,
additionalOptions = additionalOptions == null
? const <String, String>{}
: Map.from(additionalOptions),
tileProvider = tileProvider == null
? NetworkTileProvider(
headers: {'User-Agent': 'flutter_map ($userAgentPackageName)'},
)
: (tileProvider
..headers = <String, String>{
...tileProvider.headers,
if (!tileProvider.headers.containsKey('User-Agent'))
'User-Agent': 'flutter_map ($userAgentPackageName)',
}),
tileProvider = (tileProvider ?? NetworkTileProvider())
..headers.putIfAbsent(
'User-Agent', () => 'flutter_map ($userAgentPackageName)'),
tileUpdateTransformer =
tileUpdateTransformer ?? TileUpdateTransformers.ignoreTapEvents;

Expand Down Expand Up @@ -519,19 +520,30 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
required TileBoundsAtZoom tileBoundsAtZoom,
required bool pruneAfterLoad,
}) {
final cancelLoading = Completer<void>();

final imageProvider = widget.tileProvider.supportsCancelLoading
? widget.tileProvider.getImageWithCancelLoadingSupport(
tileBoundsAtZoom.wrap(coordinates),
widget,
cancelLoading.future,
)
: widget.tileProvider.getImage(
tileBoundsAtZoom.wrap(coordinates),
widget,
);

return TileImage(
vsync: this,
coordinates: coordinates,
imageProvider: widget.tileProvider.getImage(
tileBoundsAtZoom.wrap(coordinates),
widget,
),
imageProvider: imageProvider,
onLoadError: _onTileLoadError,
onLoadComplete: (coordinates) {
if (pruneAfterLoad) _pruneIfAllTilesLoaded(coordinates);
},
tileDisplay: widget.tileDisplay,
errorImage: widget.errorImage,
cancelLoading: cancelLoading,
);
}

Expand Down Expand Up @@ -576,7 +588,7 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {

_tileImageManager.evictAndPrune(
visibleRange: visibleTileRange,
pruneBuffer: math.max(widget.panBuffer, widget.keepBuffer),
pruneBuffer: max(widget.panBuffer, widget.keepBuffer),
evictStrategy: widget.evictErrorTileStrategy,
);
}
Expand Down Expand Up @@ -631,10 +643,10 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
var result = zoom.round();

if (widget.minNativeZoom != null) {
result = math.max(result, widget.minNativeZoom!);
result = max(result, widget.minNativeZoom!);
}
if (widget.maxNativeZoom != null) {
result = math.min(result, widget.maxNativeZoom!);
result = min(result, widget.maxNativeZoom!);
}

return result;
Expand Down Expand Up @@ -674,7 +686,7 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
);
_tileImageManager.prune(
visibleRange: visibleTileRange,
pruneBuffer: math.max(widget.panBuffer, widget.keepBuffer),
pruneBuffer: max(widget.panBuffer, widget.keepBuffer),
evictStrategy: widget.evictErrorTileStrategy,
);
}
Expand Down
Loading