Back to Posts

Integrate Google Maps in Your Flutter App

9 min read

Adding Google Maps to your Flutter application can greatly enhance its functionality and user experience. This guide will walk you through the process of integrating Google Maps, from initial setup to implementing advanced features.

Prerequisites

Before we begin, you'll need:

  1. A Google Cloud Platform account
  2. API keys for both Android and iOS platforms
  3. Flutter project set up and running

Initial Setup

Step 1: Add Dependencies

Add the following to your pubspec.yaml:

dependencies:
  google_maps_flutter: ^2.5.0
  location: ^5.0.3

Step 2: Configure Platform-Specific Settings

For Android (android/app/src/main/AndroidManifest.xml):

<manifest ...>
    <application ...>
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="YOUR_API_KEY"/>
    </application>
</manifest>

For iOS (ios/Runner/AppDelegate.swift):

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("YOUR_API_KEY")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Basic Implementation

Simple Map View

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  GoogleMapController? mapController;
  
  // Initial camera position
  static final CameraPosition initialPosition = CameraPosition(
    target: LatLng(37.7749, -122.4194), // San Francisco coordinates
    zoom: 12.0,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Google Maps Integration'),
      ),
      body: GoogleMap(
        initialCameraPosition: initialPosition,
        onMapCreated: (GoogleMapController controller) {
          mapController = controller;
        },
        myLocationEnabled: true,
        myLocationButtonEnabled: true,
        mapType: MapType.normal,
        zoomControlsEnabled: true,
        zoomGesturesEnabled: true,
      ),
    );
  }
}

Adding Markers

Custom Markers Implementation

class MarkerMapScreen extends StatefulWidget {
  @override
  _MarkerMapScreenState createState() => _MarkerMapScreenState();
}

class _MarkerMapScreenState extends State<MarkerMapScreen> {
  Set<Marker> markers = {};
  
  @override
  void initState() {
    super.initState();
    _addMarkers();
  }

  void _addMarkers() {
    markers.add(
      Marker(
        markerId: MarkerId('marker_1'),
        position: LatLng(37.7749, -122.4194),
        infoWindow: InfoWindow(
          title: 'San Francisco',
          snippet: 'The Golden City',
        ),
        onTap: () {
          print('Marker tapped');
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        initialCameraPosition: CameraPosition(
          target: LatLng(37.7749, -122.4194),
          zoom: 12,
        ),
        markers: markers,
      ),
    );
  }
}

User Location

Getting Current Location

class LocationMapScreen extends StatefulWidget {
  @override
  _LocationMapScreenState createState() => _LocationMapScreenState();
}

class _LocationMapScreenState extends State<LocationMapScreen> {
  late Location location;
  LocationData? currentLocation;
  
  @override
  void initState() {
    super.initState();
    location = Location();
    _getCurrentLocation();
  }

  Future<void> _getCurrentLocation() async {
    try {
      currentLocation = await location.getLocation();
      if (currentLocation != null && mounted) {
        setState(() {});
      }
    } catch (e) {
      print('Error getting location: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: currentLocation == null
          ? Center(child: CircularProgressIndicator())
          : GoogleMap(
              initialCameraPosition: CameraPosition(
                target: LatLng(
                  currentLocation!.latitude!,
                  currentLocation!.longitude!,
                ),
                zoom: 15,
              ),
            ),
    );
  }
}

Custom Map Styling

Applying Custom Map Styles

class StyledMapScreen extends StatefulWidget {
  @override
  _StyledMapScreenState createState() => _StyledMapScreenState();
}

class _StyledMapScreenState extends State<StyledMapScreen> {
  GoogleMapController? mapController;
  String mapStyle = '''
    [
      {
        "featureType": "water",
        "elementType": "geometry",
        "stylers": [
          {
            "color": "#e9e9e9"
          }
        ]
      },
      {
        "featureType": "road",
        "elementType": "geometry",
        "stylers": [
          {
            "color": "#ffffff"
          }
        ]
      }
    ]
  ''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        initialCameraPosition: CameraPosition(
          target: LatLng(37.7749, -122.4194),
          zoom: 12,
        ),
        onMapCreated: (GoogleMapController controller) {
          mapController = controller;
          controller.setMapStyle(mapStyle);
        },
      ),
    );
  }
}

Advanced Features

Polylines and Polygons

class AdvancedMapScreen extends StatefulWidget {
  @override
  _AdvancedMapScreenState createState() => _AdvancedMapScreenState();
}

class _AdvancedMapScreenState extends State<AdvancedMapScreen> {
  Set<Polyline> polylines = {};
  Set<Polygon> polygons = {};

  @override
  void initState() {
    super.initState();
    _addPolylines();
    _addPolygons();
  }

  void _addPolylines() {
    polylines.add(
      Polyline(
        polylineId: PolylineId('route_1'),
        points: [
          LatLng(37.7749, -122.4194),
          LatLng(37.7850, -122.4074),
          LatLng(37.7946, -122.3996),
        ],
        color: Colors.blue,
        width: 5,
      ),
    );
  }

  void _addPolygons() {
    polygons.add(
      Polygon(
        polygonId: PolygonId('area_1'),
        points: [
          LatLng(37.78, -122.42),
          LatLng(37.79, -122.41),
          LatLng(37.78, -122.40),
          LatLng(37.77, -122.41),
        ],
        fillColor: Colors.red.withOpacity(0.3),
        strokeColor: Colors.red,
        strokeWidth: 2,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        initialCameraPosition: CameraPosition(
          target: LatLng(37.7749, -122.4194),
          zoom: 13,
        ),
        polylines: polylines,
        polygons: polygons,
      ),
    );
  }
}

Best Practices

  1. API Key Security: Never expose your API keys in version control
  2. Error Handling: Always implement proper error handling for location services
  3. Performance: Use markers clustering for large datasets
  4. User Experience: Implement loading indicators for map operations
  5. Permissions: Handle location permissions properly on both platforms

Common Issues and Solutions

Map Not Showing

If the map isn't displaying:

  1. Verify API keys are correctly configured
  2. Check internet connectivity
  3. Ensure the widget has a defined size
  4. Verify platform-specific configurations

Location Permission Issues

Handle location permissions properly:

Future<void> _checkLocationPermission() async {
  bool serviceEnabled = await location.serviceEnabled();
  if (!serviceEnabled) {
    serviceEnabled = await location.requestService();
    if (!serviceEnabled) {
      return;
    }
  }

  PermissionStatus permissionGranted = await location.hasPermission();
  if (permissionGranted == PermissionStatus.denied) {
    permissionGranted = await location.requestPermission();
    if (permissionGranted != PermissionStatus.granted) {
      return;
    }
  }
}

Conclusion

Google Maps integration can significantly enhance your Flutter application's functionality. By following this guide and implementing these features properly, you can create a robust mapping experience for your users. Remember to handle permissions appropriately, secure your API keys, and follow best practices for optimal performance.