Skip to content

Commit 5fb3b3b

Browse files
authored
Support Flutter 3.10 (& Rework Tile Providers) (#1512)
* Migrated to Flutter 3.10 (removed support for Flutter <3.10) * Added new entry method to (and removed deprecated entry methods from) custom network tile image provider * Migrated example app with dart fix * Reworked tile provider implementations to improve performance and meet guidelines * Removed (now unnecessary) `HttpOverrides`/zoning strategies * Removed fallback to `NetworkTileProvider` when `FileTileProvider` used on web * Removed 'Delete Old Runs' workflow to prevent deletion of built artifacts Includes breaking changes.
1 parent 578bda0 commit 5fb3b3b

16 files changed

+167
-413
lines changed

.github/workflows/delete_old_runs.yml

-16
This file was deleted.

.github/workflows/main.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
name: Analyse, Test & Build
2-
on: [push, pull_request, workflow_dispatch]
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
workflow_dispatch:
38

49
jobs:
510
score-package:

example/android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ subprojects {
2626
project.evaluationDependsOn(':app')
2727
}
2828

29-
task clean(type: Delete) {
29+
tasks.register("clean", Delete) {
3030
delete rootProject.buildDir
3131
}

example/lib/pages/network_tile_provider.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ class NetworkTileProviderPage extends StatelessWidget {
4747
padding: const EdgeInsets.all(8),
4848
child: Column(
4949
children: [
50-
Padding(
51-
padding: const EdgeInsets.only(top: 8, bottom: 8),
50+
const Padding(
51+
padding: EdgeInsets.only(top: 8, bottom: 8),
5252
child: Wrap(
53-
children: const [
53+
children: [
5454
Text(
5555
'This provider will automatically retry failed requests, unlike the other pages.'),
5656
Text(

example/pubspec.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
name: flutter_map_example
22
description: Example application for 'flutter_map' package
33
publish_to: "none"
4-
version: 4.0.0
4+
version: 5.0.0
55

66
environment:
7-
sdk: ">=2.18.0 <3.0.0"
8-
flutter: ">=3.3.0"
7+
sdk: ">=2.18.0 <4.0.0"
8+
flutter: ">=3.10.0"
99

1010
dependencies:
1111
flutter:

lib/flutter_map.dart

+3-4
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ export 'package:flutter_map/src/layer/tile_layer/tile_image.dart';
4040
export 'package:flutter_map/src/layer/tile_layer/tile_layer.dart';
4141
export 'package:flutter_map/src/layer/tile_layer/tile_provider/asset_tile_provider.dart';
4242
export 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart';
43-
export 'package:flutter_map/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart'
44-
if (dart.library.html) 'package:flutter_map/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart';
45-
export 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_io.dart'
46-
if (dart.library.html) 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_web.dart';
43+
export 'package:flutter_map/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart'
44+
if (dart.library.io) 'package:flutter_map/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart';
45+
export 'package:flutter_map/src/layer/tile_layer/tile_provider/network_tile_provider.dart';
4746
export 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart';
4847
export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart';
4948

lib/src/layer/tile_layer/tile_layer.dart

+23-23
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
1818
import 'package:flutter_map/src/layer/tile_layer/tile_display.dart';
1919
import 'package:flutter_map/src/layer/tile_layer/tile_image.dart';
2020
import 'package:flutter_map/src/layer/tile_layer/tile_image_manager.dart';
21+
import 'package:flutter_map/src/layer/tile_layer/tile_provider/asset_tile_provider.dart';
2122
import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart';
22-
import 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_io.dart'
23-
if (dart.library.html) 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_web.dart';
23+
import 'package:flutter_map/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart';
24+
import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_tile_provider.dart';
2425
import 'package:flutter_map/src/layer/tile_layer/tile_range.dart';
2526
import 'package:flutter_map/src/layer/tile_layer/tile_range_calculator.dart';
2627
import 'package:flutter_map/src/layer/tile_layer/tile_scale_calculator.dart';
2728
import 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart';
2829
import 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart';
2930
import 'package:flutter_map/src/map/flutter_map_state.dart';
31+
import 'package:http/retry.dart';
3032

3133
part 'tile_layer_options.dart';
3234

@@ -117,34 +119,32 @@ class TileLayer extends StatefulWidget {
117119

118120
/// Provider with which to load map tiles
119121
///
120-
/// The default is [NetworkNoRetryTileProvider]. Alternatively, use
121-
/// [NetworkTileProvider] for a network provider which will retry requests.
122+
/// The default is [NetworkTileProvider] which supports both IO and web
123+
/// platforms. It uses a [RetryClient] to retry failed requests, but that can
124+
/// be overriden by specifying [NetworkTileProvider.httpClient].
122125
///
123-
/// Both network providers will use some form of caching, although not reliable. For
124-
/// better options, see https://docs.fleaflet.dev/usage/layers/tile-layer#caching.
126+
/// Does not automatically cache (past Flutter's [ImageCache]). For options to
127+
/// add offline mapping, see
128+
/// https://docs.fleaflet.dev/tile-servers/offline-mapping.
125129
///
126-
/// `userAgentPackageName` is a construction parameter, which should be passed
127-
/// the application's correct package name, such as 'com.example.app'. If no
128-
/// value is passed, it defaults to 'unknown'. This parameter is used to form
129-
/// part of the 'User-Agent' header, which is important to avoid blocking by
130-
/// tile servers. Namely, the header is the following 'flutter_map (<packageName>)'.
131-
///
132-
/// Header rules are as follows, after 'User-Agent' is generated as above:
133-
///
134-
/// * If no provider is specified here, the default will be used with
135-
/// 'User-Agent' header injected (recommended)
136-
/// * If a provider is specified here with no 'User-Agent' header, that
137-
/// provider will be used and the 'User-Agent' header will be injected
138-
/// * If a provider is specified here with a 'User-Agent' header, that
139-
/// provider will be used and the 'User-Agent' header will not be changed to any created here
130+
/// `userAgentPackageName` is a [TileLayer] parameter, which should be passed
131+
/// the application's correct package name, such as 'com.example.app'. This is
132+
/// important to avoid blocking by tile servers due to high-levels of
133+
/// unidentified traffic. This is passed through to the [NetworkTileProvider]
134+
/// in a suitably formatted string, where it forms the 'User-Agent' header,
135+
/// overriding any custom user agent specified in the HTTP client. To override
136+
/// this behaviour, specify a 'User-Agent' key in the
137+
/// [NetworkTileProvider.headers] property. If no value is passed, it defaults
138+
/// to 'unknown'. This is all ignored on the web, where the 'User-Agent' header
139+
/// cannot be changed due to a limitation of Dart/browsers.
140140
///
141141
/// [AssetTileProvider] and [FileTileProvider] are alternatives to network
142142
/// providers, which use the [urlTemplate] as a path instead.
143143
/// For example, 'assets/map/{z}/{x}/{y}.png' or
144144
/// '/storage/emulated/0/map_app/tiles/{z}/{x}/{y}.png'.
145145
///
146-
/// Custom [TileProvider]s can also be used, but these will not follow the header
147-
/// rules above.
146+
/// Custom [TileProvider]s can also be used, but these will not necessarily
147+
/// follow the header rules above.
148148
final TileProvider tileProvider;
149149

150150
/// When panning the map, keep this many rows and columns of tiles before
@@ -280,7 +280,7 @@ class TileLayer extends StatefulWidget {
280280
? const <String, String>{}
281281
: Map.from(additionalOptions),
282282
tileProvider = tileProvider == null
283-
? NetworkNoRetryTileProvider(
283+
? NetworkTileProvider(
284284
headers: {'User-Agent': 'flutter_map ($userAgentPackageName)'},
285285
)
286286
: (tileProvider

lib/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart

+8-4
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
55
import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart';
66
import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart';
77

8-
/// [TileProvider] that uses [FileImage] internally on platforms other than web
8+
/// [TileProvider] to fetch tiles from the local filesystem (not asset store)
9+
///
10+
/// Uses [FileImage] internally.
911
class FileTileProvider extends TileProvider {
12+
/// [TileProvider] to fetch tiles from the local filesystem (not asset store)
13+
///
14+
/// Uses [FileImage] internally.
1015
FileTileProvider();
1116

1217
@override
13-
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) {
14-
return FileImage(File(getTileUrl(coordinates, options)));
15-
}
18+
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
19+
FileImage(File(getTileUrl(coordinates, options)));
1620
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import 'package:flutter/rendering.dart';
2+
import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
3+
import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart';
4+
import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart';
5+
6+
/// [TileProvider] to fetch tiles from the local filesystem (not asset store)
7+
///
8+
/// Stub for IO & web specific implementations.
9+
///
10+
/// This web platform does not support reading from the local filesystem, and
11+
/// therefore throws an [UnsupportedError] when [getImage] is invoked.
12+
class FileTileProvider extends TileProvider {
13+
/// [TileProvider] to fetch tiles from the local filesystem (not asset store)
14+
///
15+
/// Stub for IO & web specific implementations.
16+
///
17+
/// This web platform does not support reading from the local filesystem, and
18+
/// therefore throws an [UnsupportedError] when [getImage] is invoked.
19+
FileTileProvider();
20+
21+
@override
22+
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
23+
throw UnsupportedError(
24+
'The current platform does not have access to IO (the local filesystem), and therefore does not support `FileTileProvider`');
25+
}

lib/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,78 @@
1+
import 'dart:async';
12
import 'dart:ui';
23

34
import 'package:flutter/foundation.dart';
45
import 'package:flutter/painting.dart';
5-
import 'package:http/http.dart' as http;
6-
import 'package:http/retry.dart';
6+
import 'package:http/http.dart';
77

8-
class FMNetworkImageProvider extends ImageProvider<FMNetworkImageProvider> {
9-
/// The URL from which the image will be fetched.
8+
/// Dedicated [ImageProvider] to fetch tiles from the network
9+
class FlutterMapNetworkImageProvider
10+
extends ImageProvider<FlutterMapNetworkImageProvider> {
11+
/// The URL to fetch the tile from (GET request)
1012
final String url;
1113

12-
/// The fallback URL from which the image will be fetched.
14+
/// The URL to fetch the tile from (GET request), in the event the original
15+
/// [url] request fails
1316
final String? fallbackUrl;
1417

15-
/// The http client that is used for the requests. Defaults to a [RetryClient]
16-
/// with a [http.Client].
17-
final http.Client httpClient;
18+
/// The HTTP client to use to make network requests
19+
final BaseClient httpClient;
1820

19-
/// Custom headers to add to the image fetch request
21+
/// The headers to include with the tile fetch request
2022
final Map<String, String> headers;
2123

22-
FMNetworkImageProvider(
23-
this.url, {
24+
/// Dedicated [ImageProvider] to fetch tiles from the network
25+
FlutterMapNetworkImageProvider({
26+
required this.url,
2427
required this.fallbackUrl,
25-
http.Client? httpClient,
26-
this.headers = const {},
27-
}) : httpClient = httpClient ?? RetryClient(http.Client());
28+
required this.headers,
29+
required this.httpClient,
30+
});
2831

2932
@override
30-
ImageStreamCompleter loadBuffer(
31-
FMNetworkImageProvider key, DecoderBufferCallback decode) {
32-
return OneFrameImageStreamCompleter(_loadWithRetry(key, decode),
33-
informationCollector: () sync* {
34-
yield ErrorDescription('Image provider: $this');
35-
yield ErrorDescription('Image key: $key');
36-
});
33+
ImageStreamCompleter loadImage(
34+
FlutterMapNetworkImageProvider key,
35+
ImageDecoderCallback decode,
36+
) {
37+
final StreamController<ImageChunkEvent> chunkEvents =
38+
StreamController<ImageChunkEvent>();
39+
40+
return MultiFrameImageStreamCompleter(
41+
codec: _loadAsync(key, chunkEvents, decode),
42+
chunkEvents: chunkEvents.stream,
43+
scale: 1,
44+
debugLabel: url,
45+
informationCollector: () => [
46+
DiagnosticsProperty('URL', url),
47+
DiagnosticsProperty('Fallback URL', fallbackUrl),
48+
DiagnosticsProperty('Current provider', key),
49+
],
50+
);
3751
}
3852

3953
@override
40-
Future<FMNetworkImageProvider> obtainKey(ImageConfiguration configuration) {
41-
return SynchronousFuture<FMNetworkImageProvider>(this);
42-
}
54+
Future<FlutterMapNetworkImageProvider> obtainKey(
55+
ImageConfiguration configuration,
56+
) =>
57+
SynchronousFuture<FlutterMapNetworkImageProvider>(this);
4358

44-
Future<ImageInfo> _loadWithRetry(
45-
FMNetworkImageProvider key,
46-
DecoderBufferCallback decode, [
59+
Future<Codec> _loadAsync(
60+
FlutterMapNetworkImageProvider key,
61+
StreamController<ImageChunkEvent> chunkEvents,
62+
ImageDecoderCallback decode, {
4763
bool useFallback = false,
48-
]) async {
49-
assert(key == this);
50-
assert(useFallback == false || fallbackUrl != null);
51-
64+
}) async {
65+
final Uint8List bytes;
5266
try {
53-
final uri = Uri.parse(useFallback ? fallbackUrl! : url);
54-
final response = await httpClient.get(uri, headers: headers);
55-
56-
if (response.statusCode != 200) {
57-
throw NetworkImageLoadException(
58-
statusCode: response.statusCode, uri: uri);
59-
}
60-
61-
final codec =
62-
await decode(await ImmutableBuffer.fromUint8List(response.bodyBytes));
63-
final image = (await codec.getNextFrame()).image;
64-
65-
return ImageInfo(image: image);
66-
} catch (e) {
67-
if (!useFallback && fallbackUrl != null) {
68-
return _loadWithRetry(key, decode, true);
69-
}
70-
rethrow;
67+
bytes = await httpClient.readBytes(
68+
Uri.parse(useFallback ? fallbackUrl ?? '' : url),
69+
headers: headers,
70+
);
71+
} catch (_) {
72+
if (useFallback) rethrow;
73+
return _loadAsync(key, chunkEvents, decode, useFallback: true);
7174
}
75+
76+
return decode(await ImmutableBuffer.fromUint8List(bytes));
7277
}
7378
}

0 commit comments

Comments
 (0)