A Google Maps location picker for Flutter. Users search for a place, tap or drag a marker, or pan the map under a fixed pin. You get back a typed address you can use straight away.
Works on Android, iOS and web. Search uses Places API (New) and needs no CORS proxy.
final picked = await showMapLocationPicker(
context,
config: const MapLocationPickerConfig(apiKey: 'YOUR_API_KEY'),
);
debugPrint(picked?.formattedAddress); // 1 Ferry Building, San Francisco, CA 94111, USA- Two ways to pick. Tap or drag a marker, or keep a fixed pin in the centre while the map moves underneath (the delivery-app pattern).
- Place search with Places API (New) autocomplete. Autocomplete is billed per session, and each search's session token is reused and then closed correctly for you.
- A typed result.
PickedPlacehas the coordinates, formatted address, street, city, postal code and country code, and the coordinates are there even when geocoding fails. - Full screen or embedded. Push the picker as a route and
awaitthe result, or place it inside a form you already have. - Programmatic control through
MapLocationPickerController: move the map, jump to the user's location, change the map type, or read the current address. - Typed errors. An invalid key, an exceeded quota and a refused location
permission each arrive as a different
MapPickerErrorKind. - Built to be customised. Every visible string can be translated. The theme, bottom card, pin, buttons and markers can all be restyled or replaced, and nearby places are optional.
- AI-assistant ready. An
llms.txtAPI reference and tested copy-paste prompts.
- Screenshots
- Getting started
- Usage
- Using with AI coding assistants
- Configuration reference
- API key security and costs
- Troubleshooting
- Migrating from 3.x
- Contributing and support
![]() Tap or drag a marker |
![]() Place search |
![]() Centre pin |
![]() Dark theme |
![]() Nearby places |
![]() Embedded in a form |
![]() Localized UI |
![]() Map types |
These screenshots come from the package's real widgets, generated by tool/screenshots. A test harness can't load Google's map tiles, so the map is an illustration. In your app, the picker shows real Google Maps.
| Minimum | |
|---|---|
| Flutter | 3.38.1 |
| Dart | 3.10 |
| Android | API 24 (Flutter's default) |
| iOS | 14.0 |
| Web | Any browser Flutter supports; JS and WebAssembly builds |
macOS, Windows and Linux are not supported, because google_maps_flutter has
no desktop implementation.
dependencies:
map_location_picker: ^4.0.1Everything you need comes from one import. LatLng, MapType,
GeocodingResult and the other types are re-exported, so you don't add
google_maps_flutter yourself.
import 'package:map_location_picker/map_location_picker.dart';In the Google Cloud console, turn on billing and enable these APIs for the platforms you ship:
| API | Used for |
|---|---|
| Maps SDK for Android | The map on Android |
| Maps SDK for iOS | The map on iOS |
| Maps JavaScript API | The map on web |
| Places API (New) | Search, place details and nearby places |
| Geocoding API | Turning coordinates into addresses |
Important
Enable Places API (New), not the legacy "Places API". This package only calls the new endpoints, and Google has closed the legacy API to projects created after 1 March 2025.
Android
In android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application ...>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY"/>
...
</application>
</manifest>iOS
The Google Maps plugin requires iOS 14.0. Projects created with Flutter
3.38 target iOS 13.0, and pod install fails until you raise the target. In
ios/Podfile, uncomment the platform line and set it to
platform :ios, '14.0'. In Xcode, set Minimum Deployments for the Runner
target to 14.0. Newer Flutter templates already target a later version.
In ios/Runner/AppDelegate.swift, make two additions and leave everything
else as Flutter generated it:
import GoogleMaps // next to the existing imports
// Inside application(_:didFinishLaunchingWithOptions:), as the first line:
GMSServices.provideAPIKey("YOUR_API_KEY")Don't replace the whole file. Projects created with Flutter 3.38 register
plugins with GeneratedPluginRegistrant in that same method, and newer
templates do it in didInitializeImplicitFlutterEngine. Either way, removing
that call leaves the map without its plugin.
In ios/Runner/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Shows your location on the map so you can pick an address.</string>[!NOTE] A location picker doesn't need
NSLocationAlwaysUsageDescriptionorUIBackgroundModes: location. Leave them out unless your app needs background location for another reason, because they invite App Store review questions.
Web
In web/index.html, add the Maps JavaScript API after <base href>:
<base href="$FLUTTER_BASE_HREF">
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>Load it synchronously, as google_maps_flutter_web documents, without async
or loading=async. The web map plugin creates google.maps.Map directly, so
the API must be ready before your app starts.
That script draws the map. Search doesn't depend on it: the package calls the
Places REST API directly, and places.googleapis.com supports CORS, so no
proxy is needed.
Future<void> chooseLocation(BuildContext context) async {
final picked = await showMapLocationPicker(
context,
config: MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
onError: (e) => debugPrint('${e.kind}: ${e.message}'),
),
);
if (picked == null) return; // The user backed out.
debugPrint(picked.formattedAddress); // 1 Ferry Building, San Francisco, CA 94111, USA
debugPrint('${picked.latLng}'); // LatLng(37.7955, -122.3937), always present
debugPrint(picked.city); // San Francisco
debugPrint(picked.countryCode); // US
}That's a complete integration. Everything below is optional.
Tip
Keep the key out of source control. Pass it at build time with
flutter run --dart-define=MAPS_API_KEY=..., then read it with
const String.fromEnvironment('MAPS_API_KEY').
Marker mode is the default: the user taps the map or drags the marker.
Centre-pin mode keeps a pin fixed in the middle while the user pans the map underneath, the way most delivery and ride-hailing apps work. The address resolves once the map stops moving.
const MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
pinMode: PickerPinMode.centerPin,
bottomCardTitle: 'Where should we deliver?',
showBackButton: true,
)To draw your own pin, pass centerPinBuilder: (context, state) => .... The
PinState tells you whether the map is idle or being dragged.
const MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
startWithCurrentLocation: true,
locationTimeout: Duration(seconds: 8),
)If location services are off or permission is refused, the picker stays at
initialPosition. If no fix arrives within locationTimeout, it uses the
device's last known position, or initialPosition if there isn't one. Either
way it never opens on a blank map, and none of these fallbacks call onError.
const MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
countries: ['gb', 'ie'], // ISO 3166-1 alpha-2, up to 15
placeTypes: [PlaceType.streetAddress], // up to 5
language: 'en', // language of the returned addresses
)If you need more control, build the filter yourself. Any field you set on it takes precedence over the shortcuts above:
MapLocationPicker(
config: const MapLocationPickerConfig(apiKey: 'YOUR_API_KEY'),
searchConfig: SearchConfig(
searchFilter: AutocompleteSearchFilter(includeQueryPredictions: true),
),
)searchConfig, geoCodingConfig and controller are separate arguments that
sit beside config:. They aren't fields of MapLocationPickerConfig.
Shows places around the pin as chips the user can tap to select.
const MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
showNearbyPlaces: true,
nearbyPlacesRadius: 300, // metres
nearbyPlacesLimit: 6,
nearbyPlaceTypes: [PlaceType.restaurant, PlaceType.cafe],
)MapLocationPicker has its own Scaffold and is meant to be a full-screen
route. To put the picker inside a form, use MapLocationPickerView, which takes
the same arguments, and give it a bounded height:
SizedBox(
height: 400,
child: MapLocationPickerView(
config: MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
showMapTypeButton: false,
onMainMarkerPositionChanged: (position) => setState(() => _position = position),
onAddressDecoded: (result) => setState(() => _address = result?.formattedAddress),
),
),
)The picker geocodes its starting point without calling
onMainMarkerPositionChanged, so initialise _position to your
initialPosition (or read controller.position) rather than waiting for the
first callback.
If the page scrolls, the scroll view claims vertical drags before the map sees them. Let the map claim them first:
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
gestureRecognizers: {
Factory<OneSequenceGestureRecognizer>(() => EagerGestureRecognizer()),
},
)MapLocationPickerController is a ChangeNotifier. Create one, pass it as
controller:, and dispose of it when you're done.
final controller = MapLocationPickerController(config: config);
MapLocationPicker(config: config, controller: controller);
await controller.moveTo(const LatLng(48.8584, 2.2945));
await controller.goToCurrentLocation();
controller.setMapType(MapType.hybrid);
ListenableBuilder(
listenable: controller,
builder: (context, _) => Text(controller.address),
);
controller.dispose();Besides address, it exposes position, isLoading, result, results,
mapType, pinState, nearbyPlaces and lastError. confirm() calls
onNext just like tapping Confirm, but it skips the button's checks. Call it
only when isLoading is false (and, if you set requireGeocodedAddress, only
when result isn't null).
showMapLocationPicker returns a PickedPlace, or null if the user backs out.
| Field | Type | Notes |
|---|---|---|
latLng |
LatLng |
Always present, even if geocoding failed |
displayLabel |
String |
The best single label for the place; never null |
formattedAddress |
String? |
For example 10 Downing St, London SW1A 2AA, UK |
name |
String? |
The place name, when the user picked a search result |
streetNumber, street |
String? |
|
city / locality, administrativeArea |
String? |
|
postalCode |
String? |
|
country, countryCode |
String? |
countryCode is ISO alpha-2, for example GB |
placeId |
String? |
|
result |
GeocodingResult? |
The raw geocoding result |
place |
Place? |
The raw Places (New) result, when the user picked a search result |
If you work with a GeocodingResult directly, for example in onNext or
onAddressDecoded, the same fields are available as extension getters:
result.city, result.postalCode, result.countryCode, result.latLng and
result.component('administrative_area_level_2').
Failures reach onError as a MapLocationPickerException with a kind, a
message and, for HTTP failures, a statusCode. The one exception is the
initial lookup for startWithCurrentLocation, which falls back quietly
instead. The location kinds come from the my-location button and
controller.goToCurrentLocation().
MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
onError: (e) {
if (!e.isUserFacing) return; // cancelled requests and "no results"
final message = switch (e.kind) {
MapPickerErrorKind.requestDenied =>
'Maps is misconfigured: check the API key and enabled APIs.',
MapPickerErrorKind.network => 'You appear to be offline.',
MapPickerErrorKind.locationPermissionDeniedForever =>
'Allow location access in system settings.',
_ => e.message,
};
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
},
)The other kinds are cancelled, invalidRequest, quotaExceeded,
noResults, locationServiceDisabled, locationPermissionDenied,
locationTimeout, mapUnavailable, unsupportedPlatform and unknown.
Key problems look different depending on which API reported them:
- Search, place details and nearby places (Places API (New)) take the kind
from the HTTP status. A wrong key arrives as
invalidRequest(HTTP 400, "API key not valid"). A disabled API, disabled billing or a key restriction arrives asrequestDenied(HTTP 403). - The address lookup (Geocoding API) takes the kind from Google's
statusfield and has nostatusCode. A wrong key arrives there asrequestDenied, with the message "The provided API key is invalid." - An empty key fails search with
unknownin debug builds, because the Places client asserts before sending anything, and withrequestDeniedin release builds.
Every visible string is a field of MapLocationPickerStrings, and each has an
English default. Two exceptions are set on the config itself: bottomCardTitle,
and fabTooltip, the my-location button's tooltip (default 'My Location').
Set language as well, so Google returns addresses in the same language.
MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
language: 'fr',
bottomCardTitle: 'Où livrer ?',
fabTooltip: 'Ma position',
strings: MapLocationPickerStrings(
searchHint: 'Rechercher une adresse',
confirmAddress: "Confirmer l'adresse",
loadingAddress: 'Chargement…',
noAddressFound: 'Aucune adresse trouvée',
nearbyPlacesCount: (n) => n == 1 ? '1 adresse' : '$n adresses',
// ...and 12 more; see the API reference.
),
)If you use gen-l10n, the localization prompt adds all 18 strings to your ARB files for you.
The search bar, bottom card, buttons and sheets use your app's Theme and
ColorScheme, so they follow light and dark mode automatically. You can
override them with cardColor (a translucent colour lets the blurred map show
through), cardRadius, cardBorder, floatingControlsColor and
floatingControlsIconColor.
Google's map tiles don't switch automatically. For a dark map, pass a style: a
JSON mapStyle, or a cloudMapId that has a dark style.
MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
mapStyle: Theme.of(context).brightness == Brightness.dark ? darkMapStyleJson : null,
)MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
floatingControlsPosition: FloatingControlsPosition.bottomStart,
mainMarkerIcon: myBitmapDescriptor,
confirmButton: (context, onNext) => FilledButton(
onPressed: onNext,
child: const Text('Deliver here'),
),
bottomCardBuilder: (context, result, results, address, isLoading, onNext, searchBar) {
return MyAddressCard(address: address, loading: isLoading, onConfirm: onNext);
},
)You can also replace or hide the search bar (searchBarBuilder,
showSearchBar), the back button (backButtonBuilder) and the floating
buttons (showMapTypeButton, showMyLocationButton). You can add your own
markers with additionalMarkers, customMarkerIcons and onMarkerTapped.
PlacesAutocomplete(
config: const SearchConfig(apiKey: 'YOUR_API_KEY'),
onGetDetails: (place) => debugPrint(place?.formattedAddress),
onError: (e) => debugPrint('${e.kind}: ${e.message}'),
)AI assistants often suggest APIs from older versions of this package, or from other packages, because that's what their training data contains. Three things help them get 4.x right.
llms.txt
is a compact, authoritative reference to the 4.x API, written for language
models. It covers the platform setup, the config options most apps use, the result
type, error kinds, localization, testing, troubleshooting and a list of removed
APIs. Assistants that
can browse will read it from the link. For any other assistant, paste the file
into the chat.
Paste this into your assistant's instructions file so every future request follows it:
| Tool | File |
|---|---|
| Claude Code | CLAUDE.md |
| OpenAI Codex and other tools that read AGENTS.md | AGENTS.md |
| GitHub Copilot | .github/copilot-instructions.md |
| Cursor | .cursor/rules/map-location-picker.mdc |
| Gemini CLI | GEMINI.md |
## map_location_picker (4.x)
- API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt. Prefer it over memory; the 3.x APIs no longer exist.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew; don't add google_maps_flutter for them.
- Full-screen picking: `final picked = await showMapLocationPicker(context, config: MapLocationPickerConfig(apiKey: key));` returns `PickedPlace?`. null means the user cancelled, and `picked.latLng` is never null.
- Inside an existing layout, use `MapLocationPickerView` with a bounded height. Never put `MapLocationPicker`, which has its own Scaffold, in a Column or ListView. In a scroll view, set `gestureRecognizers: {Factory<OneSequenceGestureRecognizer>(() => EagerGestureRecognizer())}` so the map gets drags.
- Options are fields of `MapLocationPickerConfig`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`.
- Handle failures with `onError: (MapLocationPickerException e) { ... }`. Switch on `e.kind` (a `MapPickerErrorKind`), and only show UI when `e.isUserFacing`.
- Google Cloud APIs: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API, Places API (New) (not the legacy Places API) and Geocoding API.
- Never use MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types, `components:`, `result.geometry.location`, a CORS proxy, or background location permissions.
- Tests: subclass `GeoCodingConfig`, override `reverseGeocode`, and drive a `MapLocationPickerController` in plain `test()`.For Cursor, start the .mdc file with this front matter so the rule always
applies:
---
description: map_location_picker 4.x rules
alwaysApply: true
---Each prompt below is self-contained: open it, copy it, and paste it into your assistant with your project open. To test them, we gave each prompt to an assistant that had no access to this package's source, then compiled and reviewed what it produced.
Add a location picker to my app — First-time setup: dependency, Android/iOS/web configuration, and a button that returns a typed address.
Add the Flutter package map_location_picker to this app.
Goal: on the home screen, add a "Choose location" button. Tapping it opens a full-screen map picker. After the user confirms, show the picked address and coordinates on screen.
1. Add `map_location_picker: ^4.0.1` to pubspec.yaml and run `flutter pub get`.
2. Read the key with `const mapsApiKey = String.fromEnvironment('MAPS_API_KEY');` rather than hard-coding it.
3. Open the picker and await the result:
final PickedPlace? picked = await showMapLocationPicker(
context,
config: MapLocationPickerConfig(
apiKey: mapsApiKey,
onError: (MapLocationPickerException e) => debugPrint('${e.kind}: ${e.message}'),
),
);
`debugPrint` comes from `package:flutter/material.dart`, so don't add a separate foundation.dart import. null means the user backed out. Check `mounted` before using context after the await. PickedPlace has `latLng` (never null), `displayLabel` (never null), and the nullable `formattedAddress`, `street`, `city`, `postalCode` and `countryCode`.
4. Platform setup, only for the platform folders that exist:
- Android, android/app/src/main/AndroidManifest.xml: add the ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions. Inside <application>, add <meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_API_KEY"/>
- iOS: the Google Maps plugin needs iOS 14.0. If IPHONEOS_DEPLOYMENT_TARGET in ios/Runner.xcodeproj/project.pbxproj is below 14.0, raise it to 14.0. Make ios/Podfile's platform line (uncomment it if needed) match the project's target, never lower than 14.0. Never lower an existing target. In ios/Runner/AppDelegate.swift, add `import GoogleMaps` and make `GMSServices.provideAPIKey("YOUR_API_KEY")` the first line of `application(_:didFinishLaunchingWithOptions:)`. Leave the rest of the file as generated: plugin registration differs between Flutter versions, and both forms work. In ios/Runner/Info.plist, add NSLocationWhenInUseUsageDescription with a one-line reason.
- Web, web/index.html: directly after <base href="$FLUTTER_BASE_HREF">, add <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script> (synchronous: no `async` or `loading=async`, because the web map plugin needs the API loaded before the app starts)
5. End with a short reply that covers: the Google Cloud APIs to enable, every place I must replace YOUR_API_KEY, and the run command `flutter run --dart-define=MAPS_API_KEY=<your key>`.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
Build a delivery-address step — Centre-pin picker restricted to your countries, mapped into your own model, with user-facing error handling.
Using map_location_picker, build a delivery-address step for checkout.
- Create lib/checkout/delivery_step.dart containing an immutable `DeliveryAddress` model (latitude, longitude, formattedAddress, city, postalCode, countryCode) and a function `Future<DeliveryAddress?> pickDeliveryAddress(BuildContext context)`.
- The function opens the picker with `showMapLocationPicker(context, config: MapLocationPickerConfig(apiKey: ..., ...))`, which returns `PickedPlace?`. Return null when the user backs out, so the caller keeps the previous address.
- Config: `pinMode: PickerPinMode.centerPin` (the pin stays fixed and the map moves under it), `bottomCardTitle: 'Where should we deliver?'`, `startWithCurrentLocation: true`, `showBackButton: true`, and `countries: ['us']` to restrict search. Replace 'us' with the lowercase ISO 3166-1 alpha-2 codes of the countries we deliver to, up to 15.
- Build the model from `picked.latLng.latitude`, `picked.latLng.longitude`, `picked.formattedAddress`, `picked.city`, `picked.postalCode` and `picked.countryCode`. Everything except latLng can be null.
- Errors: add `onError: (MapLocationPickerException e) { ... }` to the config. Show a SnackBar with `e.message` only when `e.isUserFacing` is true. When `e.kind == MapPickerErrorKind.requestDenied`, say the API key is wrong, missing or restricted, billing is off, or Places API (New) or the Geocoding API isn't enabled. When it is `MapPickerErrorKind.invalidRequest` and the message says "API key not valid", say the key is wrong.
- Wire it into the home screen: a "Set delivery address" button that calls pickDeliveryAddress and shows the saved address.
- Read the key from `String.fromEnvironment('MAPS_API_KEY')`. At the end of your reply, list any platform setup from the facts below that the project is still missing. The location permissions are required because of startWithCurrentLocation.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
Embed the picker in an existing form — MapLocationPickerView inside a scrolling page, with a controller, state callbacks and a "Use my location" button.
Embed a map location picker directly inside this app's checkout form, instead of opening a new screen. If there is more than one candidate screen, ask me which one.
- Use `MapLocationPickerView`, not `MapLocationPicker`. `MapLocationPicker` has its own Scaffold, meant for full-screen routes, and renders squashed inside a Column or ListView. `MapLocationPickerView` takes the same arguments but needs a bounded height: `SizedBox(height: 400, child: MapLocationPickerView(config: ..., controller: ...))`. If the form scrolls, also set `gestureRecognizers: {Factory<OneSequenceGestureRecognizer>(() => EagerGestureRecognizer())}` in the config, so drags move the map instead of scrolling the page. This needs `import 'package:flutter/foundation.dart';` and `import 'package:flutter/gestures.dart';`.
- In initState, build one `MapLocationPickerConfig` and create `MapLocationPickerController(config: thatConfig)`. Pass that same config and the controller to the view, and dispose the controller in dispose().
- Config fields: `apiKey`; `showMapTypeButton: false`; `onMainMarkerPositionChanged: (LatLng p) { ... }`, which stores the coordinate with setState; and `onAddressDecoded: (GeocodingResult? r) { ... }`, which stores `r?.formattedAddress` and `_controller.position` with setState. The picker geocodes its starting point without firing `onMainMarkerPositionChanged`, so storing the position here too keeps the coordinate from being null once an address exists.
- Below the map, add a "Use my location" button that calls `controller.goToCurrentLocation()`.
- Keep the "Place order" button disabled until an address has been decoded. When it is pressed, use the stored coordinate and address; for example, show both in a SnackBar.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
Migrate from 3.x to 4.x — The Flutter bump, trimmed exports, deprecated options, the showMapLocationPicker rewrite, and behaviour changes to check.
Upgrade this app from map_location_picker 3.x to 4.x. The migration guide is https://github.com/itsarvinddev/map_location_picker/blob/master/MIGRATION_GUIDE.md. Most apps only need steps 1 and 2; the rest is cleanup and adopting the new API.
1. In pubspec.yaml, set `map_location_picker: ^4.0.1`, and set the environment to `sdk: ">=3.10.0 <4.0.0"` and `flutter: ">=3.38.1"`. 4.x cannot resolve on older Flutter versions.
2. Run `flutter pub get` and `flutter analyze`. The one breaking change is that the package import re-exports fewer symbols. If something no longer resolves, import it from its own package instead (and add that package to pubspec.yaml if the app doesn't depend on it yet). For example, legacy Places API types come from `import 'package:google_maps_apis/places.dart';`, and the Places (New) `AddressComponent` comes from `import 'package:google_maps_apis/places_new.dart' as places_new;`. `LatLng`, `MapType`, `Marker`, `GoogleMapController`, `GeocodingResult`, `AddressComponent`, `Place`, `Suggestion`, `PlacesAPINew`, `AutocompleteSearchFilter`, `LocationSettings`, `LocationPermission` and `Position` are still exported. `CancelToken`, `SuggestionsController` and http's `Client` are newly exported, so dependencies added only for those can be removed.
3. Replace these deprecated options. They still compile in 4.x but are ignored or superseded, and will be removed in 5.0.0:
- `onLocationError: (e) => ...` → `onError: (MapLocationPickerException e) => ...`, which reports every failure with a typed `kind`.
- `noAddressFoundText: '...'` → `strings: MapLocationPickerStrings(noAddressFound: '...')`.
- `SearchConfig(hideWithKeyboard: ...)` → `SearchConfig(hideOnUnfocus: ...)`.
- `bottomCardType: ...` → `cardType: ...`.
4. Where `MapLocationPicker` sits inside a Column, ListView or other layout rather than being pushed as a route, switch it to `MapLocationPickerView` and keep a bounded height. `MapLocationPicker` has its own Scaffold and renders squashed there.
5. Where the app pushes `MapLocationPicker` in a route and gets the result back through `onNext` plus `Navigator.pop`, replace the whole flow with
final picked = await showMapLocationPicker(context, config: MapLocationPickerConfig(...), searchConfig: SearchConfig(...));
Keep every existing config option. `searchConfig`, `geoCodingConfig` and `controller` are separate arguments next to `config:`, never fields of MapLocationPickerConfig. Read `picked.latLng.latitude`, `picked.latLng.longitude` and `picked.formattedAddress` instead of `result.geometry?.location`. null means the user backed out.
6. Check for these behaviour changes, and list any that affect this app in your summary rather than changing behaviour silently: `bottomCardTitle` is now actually rendered; tapping an entry in the matching-addresses sheet no longer fires `onNext` (use `onAddressSelected`); Confirm stays enabled when geocoding fails (`requireGeocodedAddress: true` restores the old behaviour); the marker is draggable (`draggableMarker: false`); `LatLng(0, 0)` is no longer treated as "unset" (`skipInitialGeocode: true`).
7. In ios/Runner/Info.plist, remove `UIBackgroundModes` → `location` and `NSLocationAlwaysUsageDescription` if they were only there for the picker. Keep `NSLocationWhenInUseUsageDescription`.
8. Run `flutter analyze` again, fix every error and deprecation warning, and summarise what changed.
If the code is older than 3.x (it uses `MapPickerConfig`, `PlacesAutocompleteConfig`, or options passed directly to `MapLocationPicker(...)` instead of `config:`), first rename those to `MapLocationPickerConfig` and `SearchConfig` and move the options into `config: MapLocationPickerConfig(...)`, following the 1.x → 2.0.0 section of the guide.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
Translate the picker with gen-l10n — ARB keys for all 18 strings (with ICU plurals) and a localized config that also localizes Google's addresses.
Translate the map_location_picker UI using this app's existing gen-l10n setup. Find the ARB files and locales yourself.
Every visible string is a field of `MapLocationPickerStrings`, passed as `strings:` inside `MapLocationPickerConfig(...)`. Here are the fields with their English defaults:
- String fields: loadingAddress 'Loading address...', loadingAddressSubtitle 'Fetching location details.', confirmAddress 'Confirm Address', noAddressFound 'No address found', mapTypeTooltip 'Map type', mapTypeTitle 'Map type', mapTypeMessage 'Select the map type you want to see.', mapTypeNormal 'Standard Map', mapTypeSatellite 'Satellite Map', mapTypeTerrain 'Terrain Map', mapTypeHybrid 'Hybrid Map', cancel 'Cancel', loadingNearbyPlaces 'Loading addresses...', tapToSelect 'tap to select', searchHint 'Search for a place or address'.
- `String Function(int count)` fields: nearbyPlacesCount and nearbyPlacesTitle, which both default to '1 matching address' / '{count} matching addresses'.
- One visible string is not in MapLocationPickerStrings: the my-location button's tooltip is `fabTooltip` on MapLocationPickerConfig itself (default 'My Location'). Translate it too. (`bottomCardTitle` is also on the config, but it is empty by default.)
- Some field names are historical. nearbyPlacesCount, nearbyPlacesTitle and loadingNearbyPlaces label the list of other addresses Google matched for the pinned point, not nearby shops. Translate the English defaults, not the field names.
1. For each locale, add an ARB key for every field plus `fabTooltip`, prefixed `mapPicker` (for example `mapPickerConfirmAddress`, `mapPickerFabTooltip`). Make the two count fields ICU plurals with a `{count}` placeholder of type `int`.
2. Where the picker is opened, build `MapLocationPickerStrings(...)` from the generated `AppLocalizations`, and pass the count fields as `nearbyPlacesCount: (n) => l10n.mapPickerNearbyPlacesCount(n)`. Set `fabTooltip: l10n.mapPickerFabTooltip` on the same `MapLocationPickerConfig`, next to `strings:`. Also set `language: Localizations.localeOf(context).languageCode` so Google returns addresses in the same language; use a bare code like 'es', never a locale name like 'es_MX'. The config depends on context, so it can't be `const`.
3. Run `flutter gen-l10n`, then `flutter analyze`, and fix any issues.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
Debug empty search results — Makes failures visible, then walks the causes in order of likelihood: key, APIs, restrictions, quota.
In this app, map_location_picker's search suggestions are always empty. Help me find out why.
1. Make failures visible. In the `MapLocationPickerConfig(...)` where the picker is opened, add:
onError: (MapLocationPickerException e) => debugPrint('map_location_picker ${e.kind}: ${e.message} (HTTP ${e.statusCode})'),
Remove `const` from the config if the analyzer requires it.
2. Check where `apiKey` gets its value. If it comes from `String.fromEnvironment`, `--dart-define-from-file` or an env file, then a plain `flutter run` passes an empty key, and that alone empties the suggestions.
3. Tell me: the exact run command for this project, including any --dart-define the key needs; what to do in the app (search starts after 3 typed characters); and the log line to look for, `map_location_picker <kind>: ...`, in the `flutter run` console.
4. Explain what each kind means for this symptom, most likely first:
- `unknown` whose message contains a failed assertion ("an apiKey must be specified"): the key is empty. Debug builds check this before sending anything; in release builds an empty key arrives as `requestDenied`.
- `invalidRequest` (HTTP 400): most often the key itself is wrong; the message says "API key not valid". Otherwise a bad `countries` or `placeTypes` value.
- `requestDenied` (HTTP 403): Places API (New) isn't enabled on the key's project (the legacy "Places API" doesn't count); or billing is off; or the key is restricted to apps and the requests lack identifying headers. For a restricted key, send the same headers both via `geocodingApiHeaders: headers` and via `placesApi: PlacesAPINew(apiKey: key, headers: headers)`. On Android the headers are `X-Android-Package` (the applicationId) and `X-Android-Cert` (the signing certificate's SHA-1 as hex with the colons removed; debug and release certificates differ). On iOS the header is `X-Ios-Bundle-Identifier`.
- `quotaExceeded` (HTTP 429): a quota is used up.
- `network`: no connectivity, or a proxy or firewall blocks places.googleapis.com.
- No error at all: fewer than 3 characters were typed, or `countries` / `placeTypes` filter out every result.
Errors with no HTTP status come from the address lookup (Geocoding API), which reports a wrong key as `requestDenied` with "The provided API key is invalid.", so read `message` as well as `kind`.
Also check the native Maps key while you're there. It does not affect search:
- Android without the `com.google.android.geo.API_KEY` meta-data: the app crashes when the map opens ("API key not found").
- iOS without a `GMSServices.provideAPIKey` call: the app also crashes when the map opens.
- Either platform with a key that is present but wrong or unauthorised: blank map, no crash.
5. Don't suggest a CORS proxy or the legacy Places API.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
Write tests for code that uses the picker — A fake geocoder and the controller, so logic is tested without rendering GoogleMap.
Write unit tests for the code in this project that uses map_location_picker. Cover every branch, including when geocoding returns nothing, and put the tests under test/.
How to test code that uses this package:
- Test through `MapLocationPickerController`. Don't pump the picker widget: `GoogleMap` can't render under `flutter test`.
- Use plain `test()`. With no map attached, `moveTo` starts a 15-second map-ready timer that only `controller.dispose()` cancels. `testWidgets()` fails if that timer is still pending when the test body ends, and `addTearDown` runs too late to prevent it, so if you ever need testWidgets, call `controller.dispose()` at the end of the test body.
- Fake the network: subclass `GeoCodingConfig`, call `super(apiKey: 'test')`, and override exactly this method:
@override
Future<(GeocodingResult?, List<GeocodingResult>)> reverseGeocode(
LatLng position, {
MapPickerErrorCallback? onErrorOverride,
}) async => ...;
- Create `MapLocationPickerController(config: const MapLocationPickerConfig(apiKey: 'test'), geoCodingConfig: fake)` and register `addTearDown(controller.dispose)`.
- Build results with `GeocodingResult(formattedAddress: '...', addressComponents: [AddressComponent(longName: '...', shortName: '...', types: ['route'])])`. The getters read these component types: `street` reads `route`; `city` reads `locality`, falling back to `postal_town`; `postalCode` reads `postal_code`; `countryCode` reads the short name of `country`.
- `await controller.moveTo(const LatLng(lat, lng), animate: false)` resolves the address. Then check `controller.result`, `controller.address` and `controller.position`.
Run `flutter test` and make sure every test passes.
Facts about map_location_picker 4.x. Trust these over what you remember; earlier versions had different APIs.
- Full API reference: https://raw.githubusercontent.com/itsarvinddev/map_location_picker/master/llms.txt (read it if you can open links).
- Requires Flutter 3.38.1+ and Dart 3.10+. Dependency line: `map_location_picker: ^4.0.1`.
- Import only `package:map_location_picker/map_location_picker.dart`. It re-exports LatLng, MapType, GeocodingResult, Place and PlacesAPINew, so don't import google_maps_flutter, google_maps_apis, geolocator or dio for them, or add those packages to pubspec.yaml.
- Picker options are fields of `MapLocationPickerConfig(apiKey: ..., ...)`, passed as `config:`. `searchConfig:`, `geoCodingConfig:` and `controller:` are separate arguments beside `config:`, never config fields.
- Platform setup: Android needs the `com.google.android.geo.API_KEY` meta-data plus the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. iOS needs a deployment target of at least 14.0, `GMSServices.provideAPIKey` in AppDelegate.swift, and NSLocationWhenInUseUsageDescription in Info.plist. Web needs `<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>` after <base href> in web/index.html, loaded synchronously (no `async`, no `loading=async`, no `libraries=places`; search runs over REST).
- Google Cloud APIs to enable, with billing on: Maps SDK for Android, Maps SDK for iOS, Maps JavaScript API (web), Places API (New) (not the legacy "Places API") and Geocoding API.
- These do not exist in 4.x: MapPickerConfig, PlacesAutocompleteConfig, google_maps_webservice types (Prediction, GoogleMapsPlaces), `components:`, reading the picked point from `result.geometry.location`. Web search needs no CORS proxy, and the picker needs no background location permission.
- When you're done, run `flutter analyze` and fix everything it reports. If you can't run commands, say so.
All options are named and optional, and all of them go on
MapLocationPickerConfig. The ones most apps use:
| Option | Default | What it does |
|---|---|---|
apiKey |
'' |
Key for the Places and Geocoding REST calls |
pinMode |
PickerPinMode.marker |
marker (tap or drag) or centerPin (the map moves under a fixed pin) |
initialPosition / initialZoom |
LatLng(28.8993, 76.6250) / 14 |
Where the map opens; set it to your market |
startWithCurrentLocation |
false |
Open at the user's location, falling back to initialPosition |
countries / placeTypes |
null |
Restrict search; up to 15 countries and 5 types |
language |
null |
Language for addresses and search results |
onError |
null |
Receives failures as a MapLocationPickerException |
onNext |
null |
Confirm button callback; not needed with showMapLocationPicker |
onAddressDecoded / onMainMarkerPositionChanged |
null |
Live updates while the user picks |
requireGeocodedAddress |
false |
When true, Confirm is disabled until an address resolves |
showNearbyPlaces |
false |
Chips for places around the pin |
strings |
English | Every visible string |
bottomCardTitle |
'' |
Heading above the address |
showBackButton / showSearchBar |
false / true |
Top bar controls |
showMapTypeButton / showMyLocationButton |
true / true |
Floating buttons |
floatingControlsPosition |
bottomEnd |
Where the floating buttons sit |
cardColor / cardRadius / cardBorder |
null |
Card colour (defaults to the theme's surface), corner radius and border |
mapStyle / cloudMapId |
null |
Map styling, for example a dark map |
geocodingApiHeaders / placesApi |
null |
Headers for restricted keys (see below) |
It also accepts the usual GoogleMap options under their normal names, such as
myLocationEnabled, zoomControlsEnabled, minMaxZoomPreference, polygons
and padding. The API reference
and llms.txt list every option.
Restricting your key to your app is strongly recommended. The map SDKs identify your app on their own, but the REST calls this package makes need identifying headers, and you must send them to both clients:
import 'package:flutter/foundation.dart';
final headers = <String, String>{
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.iOS)
'X-Ios-Bundle-Identifier': 'com.example.app',
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) ...{
'X-Android-Package': 'com.example.app',
// SHA-1 of the signing certificate in hex, colons removed.
// Debug and release certificates differ; allow both on the key.
'X-Android-Cert': '00112233445566778899AABBCCDDEEFF00112233',
},
};
MapLocationPickerConfig(
apiKey: 'YOUR_API_KEY',
geocodingApiHeaders: headers, // for the Geocoding API
placesApi: PlacesAPINew(apiKey: 'YOUR_API_KEY', headers: headers), // for Places API (New)
)The sample uses defaultTargetPlatform rather than dart:io's Platform,
which throws on web.
On web, don't send these headers. They identify a native app, while browser
keys are checked by HTTP referrer, and the Geocoding API's CORS response
doesn't allow custom headers, so the browser would block those requests. An
HTTP referrer restriction works for the Maps JavaScript API and Places API
(New), but the Geocoding API rejects referrer-restricted keys. For geocoding on
web, either route it through your own backend with geocodingBaseUrl, or pass
a separate key limited to the Geocoding API, with a quota, as
geoCodingConfig: GeoCodingConfig(apiKey: geocodingKey, language: 'fr'). A
GeoCodingConfig passed this way replaces the one built from the config, so
set language and any other geocoding options on it directly.
See Google's API security best practices.
The package reuses one session token across a search and closes it with the
details call, so you pay per session rather than per keystroke. To reduce the
Place Details cost further, request fewer fields by passing this as
searchConfig::
const SearchConfig(
placesAllFields: false,
placeFields: ['id', 'location', 'formattedAddress', 'displayName'],
)See Places API pricing.
Search suggestions are always empty
Add an onError callback and type at least three characters. For errors
from search:
unknownwith a failed assertion ("an apiKey must be specified"): the key is empty. That's the debug-build symptom; in release builds an empty key arrives asrequestDenied. A key read withString.fromEnvironmentis empty unless you pass--dart-define.invalidRequestwith "API key not valid": the key is wrong.requestDenied: Places API (New) isn't enabled, billing is off, or the key is restricted and the identifying headers are missing.quotaExceeded: a quota is used up.
Errors from the address lookup (no statusCode) come from the Geocoding API,
which reports a wrong key as requestDenied too. Read e.message to tell the
causes apart.
The map is blank, or the app crashes when it opens
- Crash on Android or iOS: the native key is missing. Without the Android
meta-data, the Maps SDK throws "API key not found". Without the iOSGMSServices.provideAPIKeycall, it throws on the first map. - Blank or grey map: the native key is invalid, restricted to a different app, or the platform's Maps SDK isn't enabled for it. The device log names the cause.
- Web: without the
<script>tag the map never initialises. The controller reportsmapUnavailablethe first time it tries to move the camera, for example to your location or to a search result, after waiting 15 seconds for the map.
Confirm does nothing or stays disabled
- Does nothing: you're using
MapLocationPickerorMapLocationPickerViewwithoutonNext. Pass one, or useshowMapLocationPicker. - Disabled: Confirm is disabled while an address is loading. If you set
requireGeocodedAddress: true, it also stays disabled until geocoding succeeds. A customconfirmButtonignores that setting.
When geocoding fails, onError says why and Confirm stays enabled by default.
showMapLocationPicker still returns the coordinate, and onNext receives
null.
The picker is squashed into a corner
MapLocationPicker is inside a Column or scroll view. Use
MapLocationPickerView with a bounded height instead.
Widgets over the map don't respond to taps on web
The map is an HTML platform view, and it takes pointer events. The package
already wraps its own overlays; wrap any widgets you stack over the map in
PointerInterceptor from
pointer_interceptor.
4.0 needs Flutter 3.38.1, re-exports fewer symbols from the package import,
deprecates four options (onLocationError, noAddressFoundText,
hideWithKeyboard and bottomCardType), and adds MapLocationPickerView for
embedding and showMapLocationPicker for getting a result directly. Most apps
need two changes: the Flutter upgrade, and a direct import for any symbol that
no longer resolves. The
migration guide covers each change, and the
migration prompt can make most of the edits
for you. The changelog lists everything else.
Issues and pull requests are welcome; see CONTRIBUTING.md.
When reporting a bug, include the onError output, because it usually names the
cause.
If this package saves you time, you can support its development:








