Hi there,
I wanted a Place Picker that would allow the user to navigate immediately to the selected location rather than having to tap another button to zoom/pan to location. So I created a custom widget using flutter_google_places with a callback function.
EDIT: since first publishing this post, I hit dependency issues. Please replace "flutter_google_places" flutter package with "flutter_google_places_hoc081098"
Here's the code:
// Automatic FlutterFlow imports
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
//import '/backend/backend.dart';
//import '/backend/schema/structs/index.dart';
//import '/actions/actions.dart' as action_blocks;
//import '/custom_code/actions/index.dart'; // Imports custom actions
import 'package:flutter_google_places_hoc081098/flutter_google_places_hoc081098.dart';
//import 'package:flutter_google_places/flutter_google_places.dart';
import 'package:google_api_headers/google_api_headers.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import '/flutter_flow/lat_lng.dart' as ffLatLng;
class LocationSearch extends StatefulWidget {
const LocationSearch({
super.key,
this.width,
this.height,
required this.showSelection,
this.onLocationSelected,
required this.iOSGoogleMapsApiKey,
required this.androidGoogleMapsApiKey,
required this.webGoogleMapsApiKey,
});
final double? width;
final double? height;
final bool showSelection;
//declare callback function onLocationSelected
final Future Function(String? selectedLocation, LatLng? selectedLatLng)?
onLocationSelected;
final String iOSGoogleMapsApiKey;
final String androidGoogleMapsApiKey;
final String webGoogleMapsApiKey;
@override
State<LocationSearch> createState() => _LocationSearchState();
}
class _LocationSearchState extends State<LocationSearch> {
String? _selectedLocation;
@override
String get googleMapsApiKey {
switch (defaultTargetPlatform) {
case TargetPlatform.macOS:
case TargetPlatform.windows:
return widget.webGoogleMapsApiKey;
case TargetPlatform.iOS:
return widget.iOSGoogleMapsApiKey;
case TargetPlatform.android:
return widget.androidGoogleMapsApiKey;
default:
return widget.webGoogleMapsApiKey;
}
}
@override
Widget build(BuildContext context) {
String? languageCode = Localizations.localeOf(context).languageCode;
// button styles set here. You could make them parameters
final buttonBackgroundColour = Color(0xFFFF5A5F);
final double buttonBorderRadius = 20;
final double buttonElevation = 2;
final buttonFontColour = Colors.white;
final double buttonFontSize = 16;
final buttonFontWeight = FontWeight.normal;
final buttonInitialText = "Select Location";
return (ElevatedButton(
child: Text(_selectedLocation ?? buttonInitialText),
//style the button
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(buttonBorderRadius)),
backgroundColor: buttonBackgroundColour,
textStyle: TextStyle(
fontSize: buttonFontSize,
color: buttonFontColour,
fontWeight: buttonFontWeight,
),
elevation: buttonElevation,
),
onPressed: () async {
final p = await PlacesAutocomplete.show(
context: context,
apiKey: googleMapsApiKey,
onError: (response) =>
print('SEARCH: Error occured when getting places response:'
'\n${response.errorMessage}'),
mode: Mode.overlay,
types: [],
components: [new Component(Component.country, "Au")],
strictbounds: false,
language: languageCode,
);
// debugPrint('SEARCH: tapped place picker button');
await displayPrediction(p, languageCode);
},
));
}
Future displayPrediction(Prediction? p, String? languageCode) async {
if (p != null && p.placeId != null) {
GoogleMapsPlaces _places = GoogleMapsPlaces(
apiKey: googleMapsApiKey,
apiHeaders: await GoogleApiHeaders().getHeaders(),
);
PlacesDetailsResponse detail =
await _places.getDetailsByPlaceId(p.placeId!);
if (detail != null &&
detail.result != null &&
detail.result.geometry != null) {
final lat = detail.result.geometry?.location?.lat;
final lng = detail.result.geometry?.location?.lng;
debugPrint('SEARCH: gotlatlng: $lat $lng');
if (lat != null && lng != null) {
//call the custom action to reload markers
final locationLatLng = ffLatLng.LatLng(lat, lng);
await widget.onLocationSelected!
.call(detail.result.name, locationLatLng);
// debugPrint('SEARCH: Completed callback');
if (widget.showSelection) {
setState(() {
_selectedLocation = detail.result.name;
});
// debugPrint('saved to state for button label: $_selectedLocation');
}
}
}
}
}
}
and here's the configuration in FF;