Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 35 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Test

on:
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.35.0'
channel: 'stable'

- name: Get dependencies
run: flutter pub get

- name: Analyze code
run: dart analyze

- name: Run tests
run: flutter test

- name: Check formatting
run: dart format --set-exit-if-changed .

- name: Check pub publish
run: dart pub publish --dry-run
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,4 @@ lib/src/Constants.dart
lib/src/constants.dart
example/lib/Constants.dart
.fvm
test/flutter_polyline_points_test.dart
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
## [3.0.1] - 2024-07-23
## [3.1.0] - 2025-09-20
- **ENHANCED**: Added validation for bicycling and walking travel modes to ensure they use RoutingPreference.unspecified as required by Google Routes API.
- Special thanks to [@enricava](https://github.com/enricava) for the contribution.
- **Fixed**: Decoding issue on web.
- **Fixed**: Decoding intermediate locations [issue](https://github.com/Dammyololade/flutter_polyline_points/pull/122).
- Thanks to [@razosx](https://github.com/razosx)

## [3.0.1] - 2025-07-23
- **NEW**: Add support for custom headers in Routes API requests.

## [3.0.0] - 2024-07-22
## [3.0.0] - 2025-07-22

### 🚀 Major Refactor - Simplified and Unified API

Expand Down
131 changes: 81 additions & 50 deletions example/lib/main.dart

Large diffs are not rendered by default.

14 changes: 2 additions & 12 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
name: example
description: A new Flutter application to describe the use of polyline_point library

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
description: A new Flutter application to describe the use of polyline_point libraryi
version: 1.0.0+1
publish_to: none

environment:
sdk: '>=3.0.0 <4.0.0'
Expand Down
30 changes: 0 additions & 30 deletions example/test/widget_test.dart

This file was deleted.

5 changes: 1 addition & 4 deletions lib/src/commons/point_lat_lng.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@


/// A pair of latitude and longitude coordinates, stored as degrees.
class PointLatLng {

/// Creates a geographical location specified in degrees [latitude] and
/// [longitude].
///
Expand All @@ -20,4 +17,4 @@ class PointLatLng {
String toString() {
return "lat: $latitude / longitude: $longitude";
}
}
}
57 changes: 39 additions & 18 deletions lib/src/network/network_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import '../utils/request_converter.dart';
/// Provides backward compatibility while enabling access to new Routes API features
// ignore_for_file: deprecated_member_use_from_same_package
class NetworkProvider {
static const String _directionsBaseUrl = 'https://maps.googleapis.com/maps/api/directions/json';
static const String _routesBaseUrl = 'https://routes.googleapis.com/directions/v2:computeRoutes';
static const String _directionsBaseUrl =
'https://maps.googleapis.com/maps/api/directions/json';
static const String _routesBaseUrl =
'https://routes.googleapis.com/directions/v2:computeRoutes';

/// Default timeout for HTTP requests
static const Duration _defaultTimeout = Duration(seconds: 30);
Expand Down Expand Up @@ -80,6 +82,8 @@ class NetworkProvider {

if (response.statusCode == 200) {
final data = json.decode(response.body);
// v2 api does not include status so use response Phrase instead
data['status'] = response.reasonPhrase;
return RoutesApiResponse.fromJson(data);
} else {
final errorData = json.decode(response.body);
Expand All @@ -103,8 +107,9 @@ class NetworkProvider {

// Add waypoints if present
if (request.wayPoints.isNotEmpty) {
final waypoints =
request.wayPoints.map((wp) => '${wp.stopOver ? '' : 'via:'}${wp.location}').join('|');
final waypoints = request.wayPoints
.map((wp) => '${wp.stopOver ? '' : 'via:'}${wp.location}')
.join('|');
queryParams['waypoints'] = waypoints;
}

Expand All @@ -118,7 +123,8 @@ class NetworkProvider {
queryParams['avoid'] = _buildAvoidString(request);
}
if (request.departureTime != null) {
queryParams['departure_time'] = (request.departureTime! ~/ 1000).toString();
queryParams['departure_time'] =
(request.departureTime! ~/ 1000).toString();
}
if (request.arrivalTime != null) {
queryParams['arrival_time'] = (request.arrivalTime! ~/ 1000).toString();
Expand All @@ -138,9 +144,12 @@ class NetworkProvider {
/// Build avoid string for Directions API
static String _buildAvoidString(PolylineRequest request) {
final avoidList = <String>[];
if (request.avoidFeatures.contains(AvoidFeature.tolls)) avoidList.add('tolls');
if (request.avoidFeatures.contains(AvoidFeature.highways)) avoidList.add('highways');
if (request.avoidFeatures.contains(AvoidFeature.ferries)) avoidList.add('ferries');
if (request.avoidFeatures.contains(AvoidFeature.tolls))
avoidList.add('tolls');
if (request.avoidFeatures.contains(AvoidFeature.highways))
avoidList.add('highways');
if (request.avoidFeatures.contains(AvoidFeature.ferries))
avoidList.add('ferries');
return avoidList.join('|');
}

Expand Down Expand Up @@ -205,14 +214,24 @@ class NetworkProvider {
points: PolylineDecoder.run(encodedPoints),
status: status,
overviewPolyline: route['overview_polyline']['points'],
totalDistanceValue:
route['legs'].map((leg) => leg['distance']['value']).reduce((v1, v2) => v1 + v2),
distanceTexts: <String>[...route['legs'].map((leg) => leg['distance']['text'])],
distanceValues: <int>[...route['legs'].map((leg) => leg['distance']['value'])],
totalDurationValue:
route['legs'].map((leg) => leg['duration']['value']).reduce((v1, v2) => v1 + v2),
durationTexts: <String>[...route['legs'].map((leg) => leg['duration']['text'])],
durationValues: <int>[...route['legs'].map((leg) => leg['duration']['value'])],
totalDistanceValue: route['legs']
.map((leg) => leg['distance']['value'])
.reduce((v1, v2) => v1 + v2),
distanceTexts: <String>[
...route['legs'].map((leg) => leg['distance']['text'])
],
distanceValues: <int>[
...route['legs'].map((leg) => leg['distance']['value'])
],
totalDurationValue: route['legs']
.map((leg) => leg['duration']['value'])
.reduce((v1, v2) => v1 + v2),
durationTexts: <String>[
...route['legs'].map((leg) => leg['duration']['text'])
],
durationValues: <int>[
...route['legs'].map((leg) => leg['duration']['value'])
],
endAddress: route["legs"].last['end_address'],
startAddress: route["legs"].first['start_address'],
);
Expand All @@ -230,7 +249,8 @@ class NetworkProvider {
mode: TravelMode.driving,
);

await getRouteBetweenCoordinates(apiKey, testRequest, timeout: Duration(seconds: 10));
await getRouteBetweenCoordinates(apiKey, testRequest,
timeout: Duration(seconds: 10));
results['directions_api'] = true;
} catch (e) {
results['directions_api'] = false;
Expand All @@ -243,7 +263,8 @@ class NetworkProvider {
destination: PointLatLng(37.7849, -122.4094),
);

await getRouteBetweenCoordinatesV2(apiKey, testRequest, timeout: Duration(seconds: 10));
await getRouteBetweenCoordinatesV2(apiKey, testRequest,
timeout: Duration(seconds: 10));
results['routes_api'] = true;
} catch (e) {
results['routes_api'] = false;
Expand Down
29 changes: 20 additions & 9 deletions lib/src/network/network_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class NetworkUtil {
headers: request.headers,
)
.timeout(request.timeoutDuration,
onTimeout: () => throw Exception("Request timed out after the specified duration."));
onTimeout: () => throw Exception(
"Request timed out after the specified duration."));

if (response.statusCode == 200) {
var parsedJson = json.decode(response.body);
Expand All @@ -37,15 +38,25 @@ class NetworkUtil {
points: PolylineDecoder.run(route["overview_polyline"]["points"]),
errorMessage: "",
status: parsedJson["status"],
totalDistanceValue:
route['legs'].map((leg) => leg['distance']['value']).reduce((v1, v2) => v1 + v2),
distanceTexts: <String>[...route['legs'].map((leg) => leg['distance']['text'])],
distanceValues: <int>[...route['legs'].map((leg) => leg['distance']['value'])],
totalDistanceValue: route['legs']
.map((leg) => leg['distance']['value'])
.reduce((v1, v2) => v1 + v2),
distanceTexts: <String>[
...route['legs'].map((leg) => leg['distance']['text'])
],
distanceValues: <int>[
...route['legs'].map((leg) => leg['distance']['value'])
],
overviewPolyline: route["overview_polyline"]["points"],
totalDurationValue:
route['legs'].map((leg) => leg['duration']['value']).reduce((v1, v2) => v1 + v2),
durationTexts: <String>[...route['legs'].map((leg) => leg['duration']['text'])],
durationValues: <int>[...route['legs'].map((leg) => leg['duration']['value'])],
totalDurationValue: route['legs']
.map((leg) => leg['duration']['value'])
.reduce((v1, v2) => v1 + v2),
durationTexts: <String>[
...route['legs'].map((leg) => leg['duration']['text'])
],
durationValues: <int>[
...route['legs'].map((leg) => leg['duration']['value'])
],
endAddress: route["legs"].last['end_address'],
startAddress: route["legs"].first['start_address'],
));
Expand Down
7 changes: 3 additions & 4 deletions lib/src/polyline_points.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ class PolylinePoints {
required RoutesApiRequest request,
Duration? timeout,
}) async {

return NetworkProvider.getRouteBetweenCoordinatesV2(
apiKey,
request,
Expand All @@ -116,8 +115,9 @@ class PolylinePoints {
}

final route = response.routes.first;
final points =
route.polylineEncoded != null ? PolylineDecoder.run(route.polylineEncoded!) : <PointLatLng>[];
final points = route.polylineEncoded != null
? PolylineDecoder.run(route.polylineEncoded!)
: <PointLatLng>[];

return PolylineResult(
points: points,
Expand Down Expand Up @@ -151,5 +151,4 @@ class PolylinePoints {
static List<PointLatLng> decodePolyline(String encodedString) {
return PolylineDecoder.run(encodedString);
}

}
12 changes: 6 additions & 6 deletions lib/src/routes_api/enums/polyline_quality.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ enum PolylineQuality {
/// High-quality polyline with detailed geometry
/// Recommended for precise route visualization
highQuality('HIGH_QUALITY'),

/// Overview polyline with simplified geometry
/// Suitable for route overviews and reduced data usage
overview('OVERVIEW');

const PolylineQuality(this.value);

/// The string value used in API requests
final String value;

/// Convert from string value to enum
static PolylineQuality? fromString(String value) {
for (PolylineQuality quality in PolylineQuality.values) {
Expand All @@ -23,7 +23,7 @@ enum PolylineQuality {
}
return null;
}

/// Get a description of the polyline quality
String get description {
switch (this) {
Expand All @@ -33,7 +33,7 @@ enum PolylineQuality {
return 'Simplified polyline for route overview with reduced data usage';
}
}

/// Recommended use case for this quality level
String get useCase {
switch (this) {
Expand All @@ -43,4 +43,4 @@ enum PolylineQuality {
return 'Route overview, list views, reduced bandwidth scenarios';
}
}
}
}
12 changes: 6 additions & 6 deletions lib/src/routes_api/enums/routing_preference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ enum RoutingPreference {

/// Prioritize routes with the shortest travel time
trafficUnaware('TRAFFIC_UNAWARE'),

/// Consider current traffic conditions for optimal time
trafficAware('TRAFFIC_AWARE'),

/// Prioritize routes that avoid traffic when possible
trafficAwareOptimal('TRAFFIC_AWARE_OPTIMAL');

const RoutingPreference(this.value);

/// The string value used in API requests
final String value;

/// Convert from string value to enum
static RoutingPreference? fromString(String value) {
for (RoutingPreference preference in RoutingPreference.values) {
Expand All @@ -27,7 +27,7 @@ enum RoutingPreference {
}
return null;
}

/// Get a human-readable description of the routing preference
String get description {
switch (this) {
Expand All @@ -41,4 +41,4 @@ enum RoutingPreference {
return 'Best route avoiding heavy traffic when possible';
}
}
}
}
Loading