Open jeneena-jose opened 4 years ago
Hi, I'll stick around, I'm struggling with the same topic
If you're still struggling with this, you can manage session tokens by creating a TokenGenerator
class that issues a new token whenever a autocomplete query is completed or expired. Using my implementation, you call TokenGenerator.token
whenever you want a token and it will return that same token until it expires. Once you are done with the search (i.e. the user selects a place), you can call TokenGenerator.done()
for the generator to clear the current token. The token is generated using UUID package.
import 'package:uuid/uuid.dart';
class TokenGenerator {
// Session token validity duration, according to Google
static const Duration _validity = Duration(seconds: 180);
static String _token;
static DateTime _lastFetched;
static String get _generate {
_lastFetched = DateTime.now();
_token = Uuid().v4();
return _token;
}
static String get token {
if (_lastFetched == null ||
_token == null ||
_lastFetched.add(_validity).isBefore(DateTime.now())) return _generate;
return _token;
}
static void done() {
_lastFetched = null;
_token = null;
}
}
Thanks @flikkr ! I will integrate UUID package and check.
Thanks @flikkr ! But actually, if i'm not mistaken the places autocomplete API returns a session token in the first response, and you can use that session token as many times as you want for autocomplete calls until the first call you make to the places details api (once a place was selected). What would be great is a way to handle these session tokens internally (capture it within the PlacesResponse object and reuse it with further autocomplete calls en place details calls)
I am not very familiar with this topic. But I don't see any session token return by the API
And Google seems to recommend to generate your own session token - https://developers.google.com/maps/documentation/places/web-service/session-tokens
I created this handy class to wrap the autocomplete() of this library :
Features :
import 'dart:async';
import 'package:google_maps_webservice/places.dart';
import 'package:uuid/uuid.dart';
class AutoCompleteSession {
static const Uuid uuid = Uuid();
static final _logger = AppLogger.getLogger(); // TODO provide your own logger or just use print()
final StreamController<List<Prediction>> _controller = StreamController();
final GoogleMapsPlaces _places;
final int _debounceDuration;
Timer? _debounce;
late String _sessionToken;
var _previousSearch = "";
AutoCompleteSession(this._places, this._debounceDuration) {
_resetToken();
}
/// Search for address
///
/// @remarks:
/// The session begins when the user starts typing a query,
/// and concludes when they select a place and a call to Place Details is made.
///
/// Each session can have multiple queries, followed by one place selection.
/// The API key(s) used for each request within a session must belong to the same Google Cloud Console project.
/// Once a session has concluded, the token is no longer valid; your app must generate a fresh token for each session.
/// If the sessiontoken parameter is omitted, or if you reuse a session token, the session is charged as if no session
/// token was provided (each request is billed separately).
/// @return bool TRUE if the autocomplete will search for value
bool search(final String input) {
if (_previousSearch == input.trim()) {
_logger.d("Skip autocomplete searching - same input detected");
return false;
}
_previousSearch = input.trim();
if (_debounce?.isActive ?? false) {
_debounce?.cancel();
}
if (_previousSearch == "") { // skip empty requests
_logger.d("Skip autocomplete searching - empty input");
return false;
}
_debounce = Timer(Duration(milliseconds: _debounceDuration), () async {
_places.autocomplete(input, sessionToken: _sessionToken, components: [Component(Component.country, "fr")], types: ["address"]).then((result) {
if (result.isOkay) {
_controller.add(result.predictions);
} else if(result.hasNoResults) {
_controller.add([]);
} else {
_logger.d(result.errorMessage);
_controller.addError(Exception(result.errorMessage));
}
}).catchError((err) {
_logger.d(err.errorMessage);
_controller.addError(Exception(err.errorMessage));
});
});
return true;
}
/// Get place details
///
/// @fields: Basic fields (cheap): address_component, adr_address, business_status, formatted_address, geometry, icon, icon_mask_base_uri, icon_background_color, name,
/// permanently_closed, photo, type, url, utc_offset, or vicinity
Future<PlacesDetailsResponse?> getDetails(final Prediction prediction) async {
final String? placeId = prediction.placeId;
if (placeId != null) {
if (_debounce?.isActive ?? false) {
_debounce?.cancel();
}
final result = await _places.getDetailsByPlaceId(placeId, sessionToken: _sessionToken, fields: ["place_id", "geometry", "formatted_address", "name", "type"]);
_resetToken();
return result;
}
return null;
}
/// Get stream
Stream<List<Prediction>> stream() {
return _controller.stream;
}
/// Close the session
void close() {
if (_debounce?.isActive ?? false) {
_debounce?.cancel();
}
if (!_controller.isClosed) {
_controller.close();
}
}
void _resetToken() {
_sessionToken = uuid.v4().toString();
_previousSearch = "";
_logger.d("Autocomplete session - resetting token !");
}
}
You can use it like this in your code :
var places = GoogleMapsPlaces(apiKey: "GOOGLE PLACES API KEY");
var autocompleteSession = AutoCompleteSession(_places, 330);
// observe results in a stream an update your UI with it
autocompleteSession.stream().listen(
(results) => /* update your ui or store here */,
onError: (err) => print("err in mapAddController"),
onDone: () => print("DONE"),
);
// to search for string input
autoCompleteSession.search(input);
// then when the user click on a predictions
autoCompleteSession.getPlaceDetails(prediction); // will reset token and cleanup :)
// don't forget to close the session when UI is disposed
autoCompleteSession.close();
Hello, Thanks for sharing such a useful package for integrating various Google APIs under one roof.
I am glad using it. But I am facing issues with pricing because in Google Autocomplete requests, there are a lot of queries generated without maintaining session tokens.
The APIs I use :
Please guide how to handle session tokens in flutter app to reduce pricing from Google Maps !