Skip to content

Commit

Permalink
feat: add direct support for debouncing TileLayer updates (fleaflet…
Browse files Browse the repository at this point in the history
…#1840)

* Add loading delay for tile layer updates fix [BUG] Bad performance in android web browser fleaflet#1839

* Refactor loading delay logic in tile layer

* dart format

* Replaced  loadingDuration Type int -> Duration, improved documentation.

* format

* update docs

---------

Co-authored-by: Joscha <[email protected]>
  • Loading branch information
ReinisSprogis and josxha authored Mar 1, 2024
1 parent e0300c0 commit c2b0d91
Showing 1 changed file with 43 additions and 2 deletions.
45 changes: 43 additions & 2 deletions lib/src/layer/tile_layer/tile_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,25 @@ class TileLayer extends StatefulWidget {
/// no affect.
final TileUpdateTransformer tileUpdateTransformer;

/// Defines the minimum delay time from last map event before the tile layers
/// are updated. This delay acts as a debounce period to prevent frequent
/// reloading of tile layers in response to rapid, successive events
/// (e.g., zooming or panning).
///
/// 16ms could be a good starting point for most applications.
/// This at 60fps this will wait one frame after the last event.
///
/// By setting this delay, we ensure that map layer updates are performed
/// only after a period of inactivity, enhancing performance and user
/// experience on lower performance devices.
///
/// - If multiple events occur within this delay period, only the last event
/// triggers the tile layer update, reducing unnecessary processing and
/// network requests.
/// - If the [loadingDelay] is `Duration.zero`, the delay is completely
/// disabled and the tile layer will update as soon as possible.
final Duration loadingDelay;

/// Create a new [TileLayer] for the [FlutterMap] widget.
TileLayer({
super.key,
Expand Down Expand Up @@ -239,6 +258,7 @@ class TileLayer extends StatefulWidget {
this.evictErrorTileStrategy = EvictErrorTileStrategy.none,
this.reset,
this.tileBounds,
this.loadingDelay = Duration.zero,
TileUpdateTransformer? tileUpdateTransformer,
String userAgentPackageName = 'unknown',
}) : assert(
Expand Down Expand Up @@ -333,6 +353,9 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
TileRangeCalculator(tileSize: widget.tileSize);
late TileScaleCalculator _tileScaleCalculator;

/// Delay Timer for [TileLayer.loadingDelay]
Timer? _delayTimer;

// We have to hold on to the mapController hashCode to determine whether we
// need to reinitialize the listeners. didChangeDependencies is called on
// every map movement and if we unsubscribe and resubscribe every time we
Expand All @@ -347,6 +370,24 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
_loadAndPruneInVisibleBounds(MapCamera.of(context));
});

/// This method is used to delay the execution of a function by the specified
/// [TileLayer.loadingDelay]. This is useful to prevent frequent reloading
/// of tile layers in response to rapid, successive events (e.g., zooming
/// or panning).
void _loadingDelay(VoidCallback action) {
//execute immediately if delay is zero.
if (widget.loadingDelay == Duration.zero) {
action();
return;
}

// Cancel the previous timer if it is still active.
_delayTimer?.cancel();

// Reset the timer to wait for the debounce duration
_delayTimer = Timer(widget.loadingDelay, action);
}

// This is called on every map movement so we should avoid expensive logic
// where possible.
@override
Expand All @@ -363,7 +404,7 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
_tileUpdateSubscription = mapController.mapEventStream
.map((mapEvent) => TileUpdateEvent(mapEvent: mapEvent))
.transform(widget.tileUpdateTransformer)
.listen(_onTileUpdateEvent);
.listen((event) => _loadingDelay(() => _onTileUpdateEvent(event)));
}

var reloadTiles = false;
Expand Down Expand Up @@ -458,7 +499,7 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
_resetSub?.cancel();
_pruneLater?.cancel();
widget.tileProvider.dispose();

_delayTimer?.cancel();
super.dispose();
}

Expand Down

0 comments on commit c2b0d91

Please sign in to comment.