Skip to content

Commit

Permalink
Add methods and classes for Places (New) API in `flutter_google_place…
Browse files Browse the repository at this point in the history
…s_sdk_platform_interface` (#87)

* Add methods and classes for Places (New) API

* Add tests for native calls

* Implement PR feedback

* add missing return in test

---------

Signed-off-by: Matan Shukry <[email protected]>
Co-authored-by: Matan Shukry <[email protected]>
  • Loading branch information
flikkr and matanshukry authored Aug 27, 2024
1 parent ab7e4a3 commit 67dfb02
Show file tree
Hide file tree
Showing 16 changed files with 548 additions and 56 deletions.
6 changes: 6 additions & 0 deletions flutter_google_places_sdk_platform_interface/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.3.0

* feat: Added support for Google Places (new) which can be enabled through `initialize` function.
* feat: Added `searchByText` and `searchNearby` function for Places (new).
* feat: Added `nameLanguageCode` and `reviews` to Place object.

## 0.2.7

* Now receiving an already converted string list for typesFilter in `findAutocompletePredictions`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ abstract class FlutterGooglePlacesSdkPlatform extends PlatformInterface {
throw UnimplementedError('deinitialize() has not been implemented.');
}

/// Initializes Places for the given application context with the given API key.
/// Initializes Places for the given application context with the given API key. Use [useNewApi] to enable the Places API (New) in the SDK
///
/// All Places API responses are localized using the device's locale.
/// This method should only be called once prior to using the Places API.
/// You may call this method again to update the API key used;
/// if so, all widgets and instances of PlacesClient will now use this new key.
Future<void> initialize(String apiKey, {Locale? locale}) {
Future<void> initialize(String apiKey, {Locale? locale, bool? useNewApi}) {
throw UnimplementedError('initialize() has not been implemented.');
}

Expand All @@ -56,7 +56,7 @@ abstract class FlutterGooglePlacesSdkPlatform extends PlatformInterface {
}

/// Updates the settings of the places client with the given API key and locale.
Future<void> updateSettings(String apiKey, {Locale? locale}) {
Future<void> updateSettings(String apiKey, {Locale? locale, bool? useNewApi}) {
throw UnimplementedError('initialize() has not been implemented.');
}

Expand Down Expand Up @@ -117,4 +117,53 @@ abstract class FlutterGooglePlacesSdkPlatform extends PlatformInterface {
}) {
throw UnimplementedError('fetchPlacePhoto() has not been implemented.');
}

/// Fetches places based on an ambiguous text query.
///
/// Only the requested [fields] will be returned. If none specified,
/// all fields will be returned.
///
/// Note that different fields can incur different billing.
///
/// For more info about billing: https://developers.google.com/maps/documentation/places/android-sdk/usage-and-billing#pricing-new
///
/// For more info on text search: https://developers.google.com/maps/documentation/places/android-sdk/text-search
Future<SearchByTextResponse> searchByText(
String textQuery, {
required List<PlaceField> fields,
String? includedType,
int? maxResultCount,
LatLngBounds? locationBias,
LatLngBounds? locationRestriction,
double? minRating,
bool? openNow,
List<int>? priceLevels,
TextSearchRankPreference? rankPreference,
String? regionCode,
bool? strictTypeFiltering,
}) {
throw UnimplementedError('fetchPlacePhoto() has not been implemented.');
}

/// Search for place(s) of interest using a location.
///
/// Only the requested [fields] will be returned. If none specified,
/// all fields will be returned.
///
/// Note that different fields can incur different billing.
///
/// For more info on nearby search: https://developers.google.com/maps/documentation/places/android-sdk/nearby-search
Future<SearchNearbyResponse> searchNearby({
required List<PlaceField> fields,
required CircularBounds locationRestriction,
List<String>? includedTypes,
List<String>? includedPrimaryTypes,
List<String>? excludedTypes,
List<String>? excludedPrimaryTypes,
NearbySearchRankPreference? rankPreference,
String? regionCode,
int? maxResultCount,
}) {
throw UnimplementedError('searchNearby() has not been implemented.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class FlutterGooglePlacesSdkMethodChannel
}

@override
Future<void> initialize(String apiKey, {Locale? locale}) {
return _invokeForSettings('initialize', apiKey, locale);
Future<void> initialize(String apiKey, {Locale? locale, bool? useNewApi}) {
return _invokeForSettings('initialize', apiKey, locale, useNewApi);
}

@override
Expand All @@ -29,19 +29,25 @@ class FlutterGooglePlacesSdkMethodChannel
}

@override
Future<void> updateSettings(String apiKey, {Locale? locale}) {
return _invokeForSettings('updateSettings', apiKey, locale);
Future<void> updateSettings(String apiKey, {Locale? locale, bool? useNewApi}) {
return _invokeForSettings('updateSettings', apiKey, locale, useNewApi);
}

Future<void> _invokeForSettings(String methodName, String apiKey, Locale? locale) {
Future<void> _invokeForSettings(
String methodName,
String apiKey,
Locale? locale,
bool? useNewApi,
) {
return _channel.invokeMethod<void>(methodName, {
'apiKey': apiKey,
'useNewApi': useNewApi ?? false,
'locale': locale == null
? null
: {
'country': locale.countryCode,
'language': locale.languageCode,
},
'country': locale.countryCode,
'language': locale.languageCode,
},
});
}

Expand Down Expand Up @@ -143,4 +149,83 @@ class FlutterGooglePlacesSdkMethodChannel
details: 'Response: $value',
);
}

@override
Future<SearchByTextResponse> searchByText(
String textQuery, {
required List<PlaceField> fields,
String? includedType,
int? maxResultCount,
LatLngBounds? locationBias,
LatLngBounds? locationRestriction,
double? minRating,
bool? openNow,
List<int>? priceLevels,
TextSearchRankPreference? rankPreference,
String? regionCode,
bool? strictTypeFiltering,
}) {
if (textQuery.isEmpty) {
throw ArgumentError('Argument query can not be empty');
}
return _channel.invokeListMethod<Map<dynamic, dynamic>>(
'searchByText',
{
'textQuery': textQuery,
'fields': fields.map((e) => e.value).toList(),
'includedType': includedType,
'maxResultCount': maxResultCount,
'locationBias': locationBias?.toJson(),
'locationRestriction': locationRestriction?.toJson(),
'minRating': minRating,
'openNow': openNow,
'priceLevels': priceLevels,
'rankPreference': rankPreference?.value,
'regionCode': regionCode,
'strictTypeFiltering': strictTypeFiltering,
},
).then(_responseFromTextSearch);
}

SearchByTextResponse _responseFromTextSearch(List<Map<dynamic, dynamic>>? value) {
final items =
value?.map((item) => item.cast<String, dynamic>()).map((map) => Place.fromJson(map)).toList(growable: false) ??
[];
return SearchByTextResponse(items);
}

@override
Future<SearchNearbyResponse> searchNearby({
required List<PlaceField> fields,
required CircularBounds locationRestriction,
List<String>? includedTypes,
List<String>? includedPrimaryTypes,
List<String>? excludedTypes,
List<String>? excludedPrimaryTypes,
NearbySearchRankPreference? rankPreference,
String? regionCode,
int? maxResultCount,
}) {
return _channel.invokeListMethod<Map<dynamic, dynamic>>(
'searchNearby',
{
'fields': fields.map((e) => e.value).toList(),
'locationRestriction': locationRestriction.toJson(),
'includedTypes': includedTypes,
'includedPrimaryTypes': includedPrimaryTypes,
'excludedTypes': excludedTypes,
'excludedPrimaryTypes': excludedPrimaryTypes,
'rankPreference': rankPreference?.value,
'regionCode': regionCode,
'maxResultCount': maxResultCount,
},
).then(_responseFromNearbySearch);
}

SearchNearbyResponse _responseFromNearbySearch(List<Map<dynamic, dynamic>>? value) {
final items =
value?.map((item) => item.cast<String, dynamic>()).map((map) => Place.fromJson(map)).toList(growable: false) ??
[];
return SearchNearbyResponse(items);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import 'package:freezed_annotation/freezed_annotation.dart';

part 'author_attribution.freezed.dart';
part 'author_attribution.g.dart';

@Freezed()
class AuthorAttribution with _$AuthorAttribution {
/// Constructs a [AuthorAttribution] object.
const factory AuthorAttribution({
/// The name of the author.
required String name,

/// The profile photo URI of the author.
required String photoUri,

/// The URI of the author.
required String uri,
}) = _AuthorAttribution;

/// Parse an [AuthorAttribution] from json.
factory AuthorAttribution.fromJson(Map<String, Object?> json) =>
_$AuthorAttributionFromJson(json);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter_google_places_sdk_platform_interface/src/types/lat_lng.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'circular_bounds.freezed.dart';
part 'circular_bounds.g.dart';

/// An immutable class representing a latitude/longitude aligned circle, with a defined radius.
@Freezed()
class CircularBounds with _$CircularBounds {
/// constructs a [CircularBounds] object.
const factory CircularBounds({
required LatLng center,
required double radius,
}) = _CircularBounds;

/// Parse an [CircularBounds] from json.
factory CircularBounds.fromJson(Map<String, Object?> json) =>
_$CircularBoundsFromJson(json);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_google_places_sdk_platform_interface/src/types/opening_h
import 'package:flutter_google_places_sdk_platform_interface/src/types/photo_metadata.dart';
import 'package:flutter_google_places_sdk_platform_interface/src/types/place_type.dart';
import 'package:flutter_google_places_sdk_platform_interface/src/types/plus_code.dart';
import 'package:flutter_google_places_sdk_platform_interface/src/types/review.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'place.freezed.dart';
Expand All @@ -28,6 +29,7 @@ class Place with _$Place {
required List<String>? attributions,
required LatLng? latLng,
required String? name,
required String? nameLanguageCode,
required OpeningHours? openingHours,
required String? phoneNumber,
required List<PhotoMetadata>? photoMetadatas,
Expand All @@ -39,6 +41,7 @@ class Place with _$Place {
required int? utcOffsetMinutes,
required LatLngBounds? viewport,
required Uri? websiteUri,
required List<Review>? reviews,
}) = _Place;

/// Parse an [Place] from json.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,28 @@ enum PlaceField {
UserRatingsTotal,
@JsonValue('UTC_OFFSET') UTCOffset,
Viewport,
WebsiteUri
;
WebsiteUri,

/// Places (new) API
CurbsidePickup,
CurrentOpeningHours,
Delivery,
DineIn,
EditorialSummary,
IconBackgroundColor,
IconUrl,
Reservable,
Reviews,
SecondaryOpeningHours,
ServesBeer,
ServesBreakfast,
ServesBrunch,
ServesDinner,
ServesLunch,
ServesVegetarianFood,
ServesWine,
Takeout,
WheelchairAccessibleEntrance;

factory PlaceField.fromJson(String name) {
name = name.toLowerCase();
Expand All @@ -39,4 +59,4 @@ enum PlaceField {

extension PlaceFieldValue on PlaceField {
String get value => _$PlaceFieldEnumMap[this]!;
}
}
Loading

0 comments on commit 67dfb02

Please sign in to comment.