iTranslated by AI
[Flutter] Google Maps for Flutter: Implementation Guide and Various Tips
GoogleMap for Flutter
google_maps_flutter | Flutter Package
This article covers the initial setup, displaying the current location, and using custom markers when creating an app that displays Google Maps in Flutter.
Development Environment
macOS Monterey version 12.1 Apple M1
iOS 15.0 iPhone 13 Pro simulator
Android API 32 simulator
Flutter SDK version: 2.10.3
Dart SDK version: 2.16.1
Environment Setup
Add google_maps_flutter: ^2.1.3 to your pubspec.yaml.
dependencies:
flutter:
sdk: flutter
...
google_maps_flutter: ^2.1.3
Obtain a Google Maps API key in advance from here.
Android
Set the API key in android/app/src/main/AndroidManifest.xml as follows:
<manifest ...
<application ...
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="YOUR KEY HERE"/>
Additionally, you need to set minSdkVersion to 20 or higher in android/app/build.gradle.
android {
defaultConfig {
minSdkVersion 20
}
}
iOS
Add the following to ios/Runner/AppDelegate.swift.
import UIKit
import Flutter
import GoogleMaps // Add
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR KEY HERE") // Add
// If the gmscore::renderer::GLState::GenBuffers error is displayed, add the following
// GMSServices.setMetalRendererEnabled(true) // See Bad Know-how for details
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Implementation
First, try the official sample
I'll try implementing and running the sample from here as it is.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapSample extends StatefulWidget {
const MapSample({Key? key}) : super(key: key);
@override
State<MapSample> createState() => MapSampleState();
}
class MapSampleState extends State<MapSample> {
final Completer<GoogleMapController> _controller = Completer();
static const CameraPosition _kGooglePlex = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);
static const CameraPosition _kLake = CameraPosition(
bearing: 192.8334901395799,
target: LatLng(37.43296265331129, -122.08832357078792),
tilt: 59.440717697143555,
zoom: 19.151926040649414);
@override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: _kGooglePlex,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _goToTheLake,
label: const Text('To the lake!'),
icon: const Icon(Icons.directions_boat),
),
);
}
Future<void> _goToTheLake() async {
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
}
}
It's OK if a screen like the one below is displayed.

Display current location
Next, I'll retrieve the current location and try setting it as the initial position when displaying the Google Map.
Necessary Packages
There are several packages for retrieving the current location, but this time I'll use
geolocator | Flutter Package.
Note: I'm also using adaptive_dialog | Flutter Package to display dialogs.
dependencies:
google_maps_flutter: ^2.1.3
geolocator: ^8.2.1
adaptive_dialog: ^1.6.3
Add Required Settings
Android
Add the following permission to AndroidManifest.xml.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
*Note: Separate configuration is required to run in the background.
iOS
Add the following to Info.plist.
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location when in the background.</string>
*Note: Separate configuration is required to run in the background.
Permission Check
enum LocationSettingResult {
serviceDisabled,
permissionDenied,
permissionDeniedForever,
enabled,
}
// Check permissions related to location information
Future<LocationSettingResult> checkLocationSetting() async {
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
logger.w('Location services are disabled.');
return Future.value(LocationSettingResult.serviceDisabled);
}
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
logger.w('Location permissions are denied.');
return Future.value(LocationSettingResult.permissionDenied);
}
}
if (permission == LocationPermission.deniedForever) {
logger.w('Location permissions are permanently denied.');
return Future.value(LocationSettingResult.permissionDeniedForever);
}
return Future.value(LocationSettingResult.enabled);
}
Future<void> recoverLocationSettings(
BuildContext context, LocationSettingResult locationResult) async {
if (locationResult == LocationSettingResult.enabled) {
return;
}
final result = await showOkCancelAlertDialog(
context: context,
okLabel: 'OK',
cancelLabel: 'Cancel',
title: 'xxxxxxx',
message: 'xxxxxxxxxxxx',
);
if (result == OkCancelResult.cancel) {
logger.w('Cancel recover location settings.');
} else {
locationResult == LocationSettingResult.serviceDisabled
? await Geolocator.openLocationSettings()
: await Geolocator.openAppSettings();
}
}
In the code above, checkLocationSetting verifies the permissions related to location information and returns the result.
The result is defined using a separate enum called LocationSettingResult.
recoverLocationSettings takes the result, shows a message to the user, and encourages them to go to the settings screen to update their settings.
For iOS

- If "Allow Once" is selected:
- It doesn't result in "permission denied," but the permission dialog will appear again on the next launch.
- If "Allow While Using App" is selected:
- It doesn't result in "permission denied," and the user won't be asked again.
- If "Don't Allow" is selected:
permission == LocationPermission.deniedForever
For Android

- If "While using the app" is selected:
- It doesn't result in "permission denied," and the user won't be asked again.
- If "Only this time" is selected:
- It doesn't result in "permission denied," but the permission dialog will appear again on the next launch.
- If "Don't allow" is selected:
- It becomes
permission == LocationPermission.denied.
- It becomes
Also, if "Use location" is turned "OFF" in the settings screen:

Geolocator.isLocationServiceEnabled() will return false.
In this specific case, use Geolocator.openLocationSettings() to display the settings screen.
Sample for Displaying Current Location
Once the preliminary checks are complete, retrieve the current location and display the Google Map at that position.
Future<LatLng> getCurrentLocation() async {
final position = await Geolocator.getCurrentPosition();
return LatLng(position.latitude, position.longitude);
}
// Modify the previous sample
class MapSampleState extends State<MapSample> {
// ....
@override
Widget build(BuildContext context) {
return FutureBuilder<LatLng>(
future: getCurrentLocation(),
builder: (BuildContext context, AsyncSnapshot<LatLng> snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return GoogleMap(
mapType: MapType.normal,
initialCameraPosition: CameraPosition(
target: snapshot.data ?? defaultLocation, zoom: 17.0),
myLocationEnabled: true,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
},
);
}
}
Displaying Custom Images for Markers
Add Custom Marker Images to your Google Maps in Flutter
If you haven't configured the assets in your pubspec.yaml, do so as follows:
assets:
- assets/
- assets/icons/
In this case, we'll proceed assuming that assets/icons/ic_marker.png has been created and will be used.
The following shows how to modify the previous sample. (Detailed error handling has been omitted.)
class MapSampleState extends State<MapSample> {
// ....
BitmapDescriptor? _markerIcon;
Future<LatLng> _initAsync(BuildContext context) async {
await _loadPinAsset();
final result = await checkLocationSetting();
if (result != LocationSettingResult.enabled) {
await recoverLocationSettings(context, result);
}
return await getCurrentLocation();
}
Future<void> _loadPinAsset() async {
_markerIcon = await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(48, 48)),
'assets/icons/ic_marker.png');
}
Marker _createMarker() {
return Marker(
markerId: const MarkerId('marker'),
position: const LatLng(xxxxx, xxxx),
icon: _markerIcon ?? BitmapDescriptor.defaultMarker,
infoWindow: const InfoWindow(title: 'title'),
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<LatLng>(
future: _initAsync(context),
builder: (BuildContext context, AsyncSnapshot<LatLng> snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return GoogleMap(
mapType: MapType.normal,
initialCameraPosition: CameraPosition(
target: snapshot.data ?? defaultLocation, zoom: 17.0),
myLocationEnabled: true,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
markers: Set<Marker>.of(<Marker>{_createMarker()}),
);
},
);
}
}
Displaying Custom Images (SVG) for Markers
Next, I'll try displaying an SVG image as a marker. We'll proceed assuming assets/icons/ic_marker.svg is created.
First, add flutter_svg to your pubspec.yaml.
flutter_svg: ^1.0.3
First, implement the logic to load the SVG image from assets as a BitmapDescriptor.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
Future<BitmapDescriptor?> bitmapDescriptorFromSvgAsset(
BuildContext context, String assetName, {int w = 32, int h = 32}) async {
String svgString = await DefaultAssetBundle.of(context).loadString(assetName);
DrawableRoot svgDrawableRoot = await svg.fromSvgString(svgString, assetName);
MediaQueryData queryData = MediaQuery.of(context);
double devicePixelRatio = queryData.devicePixelRatio;
double width = w * devicePixelRatio;
double height = h * devicePixelRatio;
ui.Picture picture = svgDrawableRoot.toPicture(size: Size(width, height));
ui.Image image = await picture.toImage(width.toInt(), height.toInt());
ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png);
return bytes != null
? BitmapDescriptor.fromBytes(bytes.buffer.asUint8List())
: null;
}
Simply change the part where the PNG image was being loaded to the following.
Future<void> _loadPinAsset() async {
_markerIcon = await bitmapDescriptorFromSvgAsset(
context, 'assets/icons/ic_marker.svg');
}
Troubleshooting (Bad Know-how)
- An
Unhandled Exceptionoccurs!
E/flutter ( 5711): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() called after dispose(): _GoogleMapsState#b9489(lifecycle state: defunct, not mounted)
E/flutter ( 5711): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter ( 5711): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter ( 5711): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
As stated in the error message, the solution is to call dispose() or check the mounted property before calling setState(), such as if (mounted) { setState() }.
- The iOS simulator occasionally crashes with
EXC_BAD_ACCESS
If you check the error log and find entries related to gmscore::renderer::GLState::GenBuffers, adding the following may resolve the issue:
GMSServices.setMetalRendererEnabled(true)
Reference URLs
- How to register and use images as assets | Flat Flutter
- Useful libraries I used after a year of development with Flutter - Qiita
- Displaying current location with Flutter + Google Maps API - Qiita
- What you can and cannot do using Google Map in Flutter (2019 edition) - Qiita
- Add Custom Marker Images to your Google Maps in Flutter
- Notes on handling location in Flutter
- How to get smartphone location information in Flutter
Discussion
GMSServices.setMetalRendererEnabled(true)
このコードがあってアプリがクラッシュしていましたので、エラーが出ない場合は書かなくてもいいって書いて欲しいです。。
2時間溶かしました笑
コメントありがとうございます。助かります!
記事修正しました。