Skip to content

feat: add built-in caching #2082

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

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bafa5f0
Remove 'vector_math' dependency
JaffaKetchup Apr 30, 2025
cfd187b
Initial caching implementation
JaffaKetchup May 3, 2025
6007304
Improved performance of persistent registry writer & format efficiency
JaffaKetchup May 4, 2025
6fe737c
Refactored to re-enable web support
JaffaKetchup May 4, 2025
25ebb25
Improved startup performance by not waiting for caching instance
JaffaKetchup May 4, 2025
b79cb07
Exposed `MapTileCachingManager` & `CachedMapTileMetadata`
JaffaKetchup May 4, 2025
92c37cb
Added `MapCachingOptions.cacheKeyGenerator`
JaffaKetchup May 4, 2025
985ba4c
Merge branch 'master' into caching
JaffaKetchup May 9, 2025
c1a8bb6
Reverted to waiting for manager instance to be created before returni…
JaffaKetchup May 10, 2025
bd5dfe7
Added size monitor
JaffaKetchup May 14, 2025
2c38eb8
Major refactoring & renaming
JaffaKetchup May 15, 2025
e9c4465
Fixed linting issue
JaffaKetchup May 15, 2025
e67f7e4
Improved speed and efficiency of size limiter
JaffaKetchup May 15, 2025
36d7268
Removed leftover debugging tools
JaffaKetchup May 15, 2025
5fe9133
Improved documentation
JaffaKetchup May 17, 2025
192860c
Switch to FlatBuffers instead of JSON
JaffaKetchup May 18, 2025
9321d0c
Return number of tiles loaded from `isInitialised`
JaffaKetchup May 19, 2025
fd7a79b
Added call to `WidgetsFlutterBinding.ensureInitialized` internally
JaffaKetchup May 19, 2025
0997c70
Drop default cache size limit to 800 MB
JaffaKetchup May 19, 2025
5547146
Minor general improvements
JaffaKetchup May 19, 2025
db50851
Replace `catch (_)` with `on Exception`
JaffaKetchup May 19, 2025
92452f6
Switched back to JSON from FlatBuffers
JaffaKetchup May 20, 2025
92a60b1
Fixed size limiter
JaffaKetchup May 20, 2025
3fe5adc
Minor improvement
JaffaKetchup May 20, 2025
6078fe2
Fixed minor bug in example app
JaffaKetchup May 22, 2025
37da3d4
Improved network tile image provider
JaffaKetchup May 23, 2025
c4b2bf4
Merge branch 'master' into caching
JaffaKetchup May 25, 2025
2548269
Replace the JSON registry with a mechanism to store metadata within t…
JaffaKetchup May 27, 2025
56bb2a6
Discard changes to example/lib/main.dart
JaffaKetchup May 27, 2025
5c4899b
Fixed bug
JaffaKetchup May 27, 2025
53e584f
Improved resilience to corruption
JaffaKetchup May 28, 2025
16c1827
Improved resilience to corruption
JaffaKetchup May 29, 2025
5f85c71
Fix unintended change
JaffaKetchup May 29, 2025
3b350dc
Fixed minor bug
JaffaKetchup May 31, 2025
e6ba6d8
Minor improvements & bug fixes
JaffaKetchup Jun 1, 2025
35d0607
Move default `tileKeyGenerator` implementation into a static util method
JaffaKetchup Jun 2, 2025
8497204
Discard changes to lib/src/layer/tile_layer/tile_layer.dart
JaffaKetchup Jun 8, 2025
ac26dde
Merge branch 'master' into caching
JaffaKetchup Jun 8, 2025
5c78329
Prevent fallback tiles from being cached
JaffaKetchup Jun 8, 2025
507ff51
Expose `calculateStaleAt` via `CachedMapTileMetadata.fromHttpHeaders`…
JaffaKetchup Jun 8, 2025
691341a
Merge branch 'master' into caching
JaffaKetchup Jun 15, 2025
7d0a1a7
Added uninitialisation & cache deletion support
JaffaKetchup Jun 15, 2025
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
9 changes: 7 additions & 2 deletions example/lib/misc/tile_providers.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_cancellable_tile_provider/flutter_map_cancellable_tile_provider.dart';
import 'package:http/http.dart';
import 'package:http/retry.dart';
//import 'package:flutter_map_cancellable_tile_provider/flutter_map_cancellable_tile_provider.dart';

final httpClient = RetryClient(Client());

TileLayer get openStreetMapTileLayer => TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
// Use the recommended flutter_map_cancellable_tile_provider package to
// support the cancellation of loading tiles.
tileProvider: CancellableNetworkTileProvider(),
// TODO: change
tileProvider: NetworkTileProvider(httpClient: httpClient),
);
13 changes: 10 additions & 3 deletions example/lib/widgets/drawer/menu_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ class MenuDrawer extends StatelessWidget {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
Container(
padding: const EdgeInsets.fromLTRB(16, 32, 16, 16)
.add(EdgeInsets.only(top: MediaQuery.paddingOf(context).top)),
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
border: Border(bottom: Divider.createBorderSide(context)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expand All @@ -65,11 +71,12 @@ class MenuDrawer extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 8),
if (kIsWeb)
const Text(
Text(
_isWASM ? 'Running with WASM' : 'Running without WASM',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
Expand Down
72 changes: 64 additions & 8 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
dart_earcut:
dependency: transitive
description:
Expand Down Expand Up @@ -89,6 +97,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
Expand Down Expand Up @@ -171,26 +187,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
version: "11.0.1"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.9"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
Expand Down Expand Up @@ -255,6 +271,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
Expand Down Expand Up @@ -380,6 +420,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
Expand Down Expand Up @@ -500,8 +548,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_math:
dependency: "direct main"
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
Expand Down Expand Up @@ -541,5 +597,5 @@ packages:
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.7.0 <4.0.0"
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.27.0"
1 change: 0 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ dependencies:
proj4dart: ^2.1.0
shared_preferences: ^2.3.4
url_launcher: ^6.3.1
vector_math: ^2.1.4

dependency_overrides:
flutter_map:
Expand Down
13 changes: 9 additions & 4 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,16 @@ export 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_display.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_image.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_layer.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/asset_tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/asset/provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart'
if (dart.library.io) 'package:flutter_map/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network_tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/file/stub_tile_provider.dart'
if (dart.library.io) 'package:flutter_map/src/layer/tile_layer/tile_provider/file/native_tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/built_in/built_in_caching_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/caching_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/disabled/disabled_caching_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/tile_metadata.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/tile_read_failure_exception.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network/tile_provider.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart';
export 'package:flutter_map/src/map/camera/camera.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/geo/latlng_bounds.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:math';

import 'package:flutter_map/src/misc/deg_rad_conversions.dart';
import 'package:latlong2/latlong.dart';
import 'package:vector_math/vector_math_64.dart';

/// Data structure representing rectangular bounding box constrained by its
/// northwest and southeast corners
Expand Down
2 changes: 1 addition & 1 deletion lib/src/gestures/map_interactive_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/src/misc/deg_rad_conversions.dart';
import 'package:flutter_map/src/misc/extensions.dart';
import 'package:latlong2/latlong.dart';
import 'package:vector_math/vector_math_64.dart';

part 'package:flutter_map/src/gestures/compound_animations.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ class FileTileProvider extends TileProvider {
@override
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
throw UnsupportedError(
'The current platform does not have access to IO (the local filesystem), and therefore does not support `FileTileProvider`');
'The current platform does not have access to IO (the local '
'filesystem), and therefore does not support `FileTileProvider`',
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/built_in/impl/stub.dart'
if (dart.library.io) 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/built_in/impl/native/native.dart'
if (dart.library.js_interop) 'package:flutter_map/src/layer/tile_layer/tile_provider/network/caching/built_in/impl/web/web.dart';
import 'package:uuid/data.dart';
import 'package:uuid/rng.dart';
import 'package:uuid/uuid.dart';

/// Simple built-in map caching using an I/O storage mechanism, for native
/// (non-web) platforms only
///
/// Stores tiles as files identified with keys, containing some metadata headers
/// followed by the tile bytes, alongside a file used to track the size of the
/// cache.
///
/// Usually uses HTTP headers to determine tile freshness, although
/// `overrideFreshAge` can override this.
///
/// This is enabled by default in flutter_map, when using the
/// [NetworkTileProvider] (or cancellable version).
///
/// It is safe to use all public methods when running on web - they will noop.
///
/// For more information, see the online documentation.
abstract interface class BuiltInMapCachingProvider
implements MapCachingProvider {
/// If an instance exists, return it, otherwise create a new instance
///
/// The provided configuration will only be respected if an instance does not
/// already exist.
///
/// See individual properties for more information about configuration.
factory BuiltInMapCachingProvider.getOrCreateInstance({
/// Path to the directory to use to store cached tiles & other related files
///
/// The provider actually uses the 'fm_cache' directory created as a child
/// of the path specified here.
///
/// The program must have rights/permissions to access the path.
///
/// The path does not have to exist, it will be recursively created if
/// missing.
///
/// All files and directories within the path will be liable to deletion by
/// the size reducer.
///
/// Defaults to a platform provided cache directory, which may be cleared by
/// the OS at any time.
String? cacheDirectory,

/// Maximum total size of cached tiles, in bytes
///
/// This is applied only when the instance is created, by running the size
/// reducer. This runs in the background (and so does not delay reads or
/// writes). The cache size may exceed this limit while the program is
/// running.
///
/// Disabling the size limit may improve write performance.
///
/// Defaults to 1 GB. Set to `null` to disable.
int? maxCacheSize = 1_000_000_000,

/// Function to convert a tile's URL to a key used to uniquely identify the
/// tile
///
/// Where parts of the URL are volatile or do not represent the tile's
/// contents/image - for example, API keys contained with the query
/// parameters - this should be modified to remove the volatile portions.
///
/// Keys must be usable as filenames on all intended platform filesystems.
/// The callback should not throw.
///
/// Defaults to using [uuidTileKeyGenerator], which custom implementations
/// may utilise.
String Function(String url)? tileKeyGenerator,

/// Override the duration of time a tile is considered fresh for
///
/// Defaults to `null`: use duration calculated from each tile's HTTP
/// headers.
Duration? overrideFreshAge,

/// Prevent any tiles from being added or updated
///
/// Does not disable the size reducer if the cache size is larger than
/// `maxCacheSize`.
///
/// Defaults to `false`.
bool readOnly = false,
}) {
assert(
maxCacheSize == null || maxCacheSize > 0,
'`maxCacheSize` must be greater than 0 or disabled',
);
assert(
overrideFreshAge == null || overrideFreshAge > Duration.zero,
'`overrideFreshAge` must be greater than 0 or disabled',
);
return _instance ??= BuiltInMapCachingProviderImpl.create(
cacheDirectory: cacheDirectory,
maxCacheSize: maxCacheSize,
overrideFreshAge: overrideFreshAge,
tileKeyGenerator: tileKeyGenerator ?? uuidTileKeyGenerator,
readOnly: readOnly,
resetSingleton: () => _instance = null,
);
}

static BuiltInMapCachingProviderImpl? _instance;

/// Destroy this caching provider instance
///
/// This means that all workers will be terminated and caching will be
/// unavailable until the next time
/// [BuiltInMapCachingProvider.getOrCreateInstance] is called (which may be
/// on the next tile load by default).
///
/// If [deleteCache] is `true` (defaults to `false`), then the entire
/// `cacheDirectory` and its contents will be deleted.
///
/// Completes when fully uninitialised. It is not necessary to wait for this
/// to complete before calling [BuiltInMapCachingProvider.getOrCreateInstance]
/// again (to create a new instance).
///
/// ---
///
/// It is usually safe to let the caching provider 'naturally' terminate with
/// the program when the system terminates the program after the app is
/// closed. Therefore, this method is not required to be called at the end of
/// the app's lifecycle.
///
/// This method is provided to:
/// * allow cache provider's configuration to be changed
/// * allow the cache to be deleted from within the app
/// * facilitate testing
///
/// Note that the cache may also be deleted when the program is not running
/// by deleteting the `cacheDirectory` - for example, by the user choosing to
/// clear the app's cache through the operating system (by default).
Future<void> destroy({bool deleteCache = false});

/// Default `tileKeyGenerator` which generates v5 UUIDs from input strings
///
/// May be utilised in custom `tileKeyGenerator` implementations.
///
/// See [BuiltInMapCachingProvider.getOrCreateInstance]'s parameter for more
/// info.
static String uuidTileKeyGenerator(String url) =>
_uuid.v5(Namespace.url.value, url);
static final _uuid = Uuid(goptions: GlobalOptions(MathRNG()));
}
Loading