Skip to content

Commit 1c6ff7b

Browse files
committed
Add partial FMTCCachingProvider, depending on flutter_map branch
1 parent 01b799f commit 1c6ff7b

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:flutter/foundation.dart';
4+
import 'package:flutter_map/flutter_map.dart';
5+
6+
import '../../../flutter_map_tile_caching.dart';
7+
import '../../backend/export_internal.dart';
8+
9+
@immutable
10+
class FMTCCachingProvider implements MapCachingProvider, PutTileCapability {
11+
/// Create an [FMTCTileProvider] that interacts with a subset of all available
12+
/// stores
13+
///
14+
/// See [stores] & [otherStoresStrategy] for information.
15+
///
16+
/// {@macro fmtc.fmtcTileProvider.constructionTip}
17+
const FMTCCachingProvider({
18+
required this.stores,
19+
this.otherStoresStrategy,
20+
this.loadingStrategy = BrowseLoadingStrategy.cacheFirst,
21+
this.useOtherStoresAsFallbackOnly = false,
22+
this.cachedValidDuration = Duration.zero,
23+
this.tileKeyGenerator = BuiltInMapCachingProvider.uuidTileKeyGenerator,
24+
});
25+
26+
/// Create an [FMTCTileProvider] that interacts with all available stores,
27+
/// using one [BrowseStoreStrategy] efficiently
28+
///
29+
/// {@macro fmtc.fmtcTileProvider.constructionTip}
30+
const FMTCCachingProvider.allStores({
31+
required BrowseStoreStrategy allStoresStrategy,
32+
this.loadingStrategy = BrowseLoadingStrategy.cacheFirst,
33+
this.cachedValidDuration = Duration.zero,
34+
this.tileKeyGenerator = BuiltInMapCachingProvider.uuidTileKeyGenerator,
35+
}) : stores = const {},
36+
otherStoresStrategy = allStoresStrategy,
37+
useOtherStoresAsFallbackOnly = false;
38+
39+
/// The store names from which to (possibly) read/update/create tiles from/in
40+
///
41+
/// Keys represent store names, and the associated [BrowseStoreStrategy]
42+
/// represents how that store should be used.
43+
///
44+
/// Stores not included will not be used by default. However,
45+
/// [otherStoresStrategy] determines whether & how all other unspecified
46+
/// stores should be used. Stores included in this mapping but with a `null`
47+
/// value will be exempted from [otherStoresStrategy] (ie. unused).
48+
///
49+
/// All specified store names should correspond to existing stores.
50+
/// Non-existant stores may cause unexpected read behaviour and will throw a
51+
/// [StoreNotExists] error if a tile is attempted to be written to it.
52+
final Map<String, BrowseStoreStrategy?> stores;
53+
54+
/// The behaviour of all other stores not specified in [stores]
55+
///
56+
/// `null` means that all other stores will not be used.
57+
///
58+
/// Setting a non-`null` value may negatively impact performance, because
59+
/// internal tile cache lookups will have less constraints.
60+
///
61+
/// Also see [useOtherStoresAsFallbackOnly] for whether these unspecified
62+
/// stores should only be used as a last resort or in addition to the
63+
/// specified stores as normal.
64+
///
65+
/// Stores specified in [stores] but associated with a `null` value will not
66+
/// gain this behaviour.
67+
final BrowseStoreStrategy? otherStoresStrategy;
68+
69+
/// Determines whether the network or cache is preferred during browse
70+
/// caching, and how to fallback
71+
///
72+
/// Defaults to [BrowseLoadingStrategy.cacheFirst].
73+
final BrowseLoadingStrategy loadingStrategy;
74+
75+
/// Whether to only use tiles retrieved by
76+
/// [FMTCTileProvider.otherStoresStrategy] after all specified stores have
77+
/// been exhausted (where the tile was not present)
78+
///
79+
/// When tiles are retrieved from other stores, it is counted as a miss for
80+
/// the specified store(s).
81+
///
82+
/// Note that an attempt is *always* made to read the tile from the cache,
83+
/// regardless of whether the tile is then actually retrieved from the cache
84+
/// or the network is then used (successfully).
85+
///
86+
/// For example, if a specified store does not contain the tile, and an
87+
/// unspecified store does contain the tile:
88+
/// * if this is `false`, then the tile will be retrieved and used from the
89+
/// unspecified store
90+
/// * if this is `true`, then the tile will be retrieved (see note above),
91+
/// but not used unless the network request fails
92+
///
93+
/// Defaults to `false`.
94+
final bool useOtherStoresAsFallbackOnly;
95+
96+
/// The duration for which a tile does not require updating when cached, after
97+
/// which it is marked as expired and updated at the next possible
98+
/// opportunity
99+
///
100+
/// Set to [Duration.zero] to never expire a tile (default).
101+
final Duration cachedValidDuration;
102+
103+
/// Function to convert a tile's URL to a key used to uniquely identify the
104+
/// tile
105+
///
106+
/// Where parts of the URL are volatile or do not represent the tile's
107+
/// contents/image - for example, API keys contained with the query
108+
/// parameters - this should be modified to remove the volatile portions.
109+
///
110+
/// Keys must be usable as filenames on all intended platform filesystems.
111+
/// The callback should not throw.
112+
///
113+
/// Defaults to using [BuiltInMapCachingProvider.uuidTileKeyGenerator].
114+
final String Function(String url) tileKeyGenerator;
115+
116+
/// Compile the [FMTCTileProvider.stores] &
117+
/// [FMTCTileProvider.otherStoresStrategy] into a format which can be resolved
118+
/// by the backend once all available stores are known
119+
({List<String> storeNames, bool includeOrExclude}) _compileReadableStores() {
120+
final excludeOrInclude = otherStoresStrategy != null;
121+
final storeNames = (excludeOrInclude
122+
? stores.entries.where((e) => e.value == null)
123+
: stores.entries.where((e) => e.value != null))
124+
.map((e) => e.key)
125+
.toList(growable: false);
126+
return (storeNames: storeNames, includeOrExclude: !excludeOrInclude);
127+
}
128+
129+
@override
130+
Future<CachedMapTile<CachedTileMetadata>?> getTile(String url) async {
131+
final storageSuitableUid = tileKeyGenerator(url);
132+
133+
final (
134+
tile: existingTile,
135+
intersectedStoreNames: intersectedExistingStores, //! TODO
136+
allStoreNames: allExistingStores,
137+
) = await FMTCBackendAccess.internal.readTile(
138+
url: storageSuitableUid,
139+
storeNames: _compileReadableStores(),
140+
);
141+
142+
if (existingTile != null) {
143+
final bytes = existingTile.bytes;
144+
145+
if (loadingStrategy == BrowseLoadingStrategy.cacheOnly) {
146+
// We can't use the network, so always use the tile regardless of its
147+
// status
148+
return (bytes: bytes, metadata: CachedTileMetadata.fresh);
149+
}
150+
151+
if (loadingStrategy == BrowseLoadingStrategy.onlineFirst) {
152+
// Prefer using the network, but use the cached tile as a fallback if
153+
// necessary
154+
return (bytes: bytes, metadata: CachedTileMetadata.stale);
155+
}
156+
157+
// Loading strategy is cache first
158+
// Here, we want to try to use the network (falling back to the cached
159+
// tile), only if either:
160+
// * the tile was retrieved from an "other" store, and
161+
// `useOtherStoresAsFallbackOnly` was set
162+
// * the tile has expired and needs updating
163+
// Otherwise, we use the cached tile
164+
165+
if (useOtherStoresAsFallbackOnly &&
166+
stores.keys.toSet().intersection(allExistingStores.toSet()).isEmpty) {
167+
return (bytes: bytes, metadata: CachedTileMetadata.stale);
168+
}
169+
if (cachedValidDuration != Duration.zero &&
170+
DateTime.timestamp().millisecondsSinceEpoch -
171+
existingTile.lastModified.millisecondsSinceEpoch >
172+
cachedValidDuration.inMilliseconds) {
173+
return (bytes: bytes, metadata: CachedTileMetadata.stale);
174+
}
175+
return (bytes: bytes, metadata: CachedTileMetadata.fresh);
176+
}
177+
178+
if (loadingStrategy == BrowseLoadingStrategy.cacheOnly) {
179+
throw FMTCBrowsingError(
180+
type: FMTCBrowsingErrorType.missingInCacheOnlyMode,
181+
networkUrl: url,
182+
storageSuitableUID: storageSuitableUid,
183+
);
184+
}
185+
186+
return null; // Depend on network
187+
}
188+
189+
@override
190+
// TODO: implement isSupported
191+
bool get isSupported => throw UnimplementedError();
192+
193+
@override
194+
Future<void> putTile({
195+
required String url,
196+
Uint8List? bytes,
197+
}) {
198+
// TODO: implement putTile
199+
throw UnimplementedError();
200+
}
201+
}

pubspec.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ dev_dependencies:
4747
objectbox_generator: ^4.1.0
4848
test: ^1.25.15
4949

50+
dependency_overrides:
51+
flutter_map:
52+
git:
53+
url: https://github.com/fleaflet/flutter_map
54+
ref: modern-tile-layer
55+
5056
flutter: null
5157

5258
objectbox:

0 commit comments

Comments
 (0)