Google Maps SDK Integration in Mobile App
Google Maps SDK for Android and iOS — mature tool, but has nuances that regularly break first integrations: improper API key initialization, version incompatibilities with Gradle / Swift Package Manager, and gray map display without clear error message in logs.
First Run: API Key and Restrictions
API key created in Google Cloud Console, project must include Maps SDK for Android and Maps SDK for iOS as separate products. Key should be restricted: for Android — by applicationId (SHA-1 fingerprint + package name), for iOS — by Bundle ID. Without restrictions key can be extracted from APK by decompiler in 5 minutes.
On Android key written in AndroidManifest.xml:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
Value MAPS_API_KEY set in local.properties and substituted via buildConfigField in build.gradle — don't hardcode string directly.
On iOS — in AppDelegate or via GMSServices.provideAPIKey() before creating any GMSMapView:
import GoogleMaps
@main
struct AppEntry: App {
init() {
GMSServices.provideAPIKey("YOUR_API_KEY")
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
Gray Map — What to Check
- API key doesn't have rights to needed product (Maps SDK vs Maps JavaScript API — different products)
- Billing not connected to project in GCP (Maps SDK requires active billing, even within free tier)
- On Android —
minSdkVersionbelow 21 or missingcom.google.android.gms:play-services-mapsdependency - On iOS —
GoogleMaps.xcframeworkfile not added toFrameworks, Libraries, and Embedded Content
Android: Basic MapView Setup
// build.gradle (app)
implementation("com.google.android.gms:play-services-maps:18.2.0")
// Fragment
class MapFragment : Fragment(), OnMapReadyCallback {
private lateinit var map: GoogleMap
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val mapFragment = childFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
map.uiSettings.isZoomControlsEnabled = true
map.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(55.7558, 37.6173), // Moscow
12f
)
)
}
}
SupportMapFragment preferred over MapView — it manages lifecycle (onCreate, onResume, onPause, onDestroy) itself. If using MapView directly — must manually forward each lifecycle method, forgotten mapView.onDestroy() causes memory leak.
iOS: SwiftUI and UIKit
In UIKit — GMSMapView added as regular UIView. In SwiftUI — wrap via UIViewRepresentable:
struct GoogleMapView: UIViewRepresentable {
let coordinate: CLLocationCoordinate2D
let zoom: Float
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition(target: coordinate, zoom: zoom)
let mapView = GMSMapView(frame: .zero, camera: camera)
mapView.isMyLocationEnabled = true
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Context) {
let camera = GMSCameraPosition(target: coordinate, zoom: zoom)
mapView.animate(to: camera)
}
}
Custom Map Styles
Google Maps supports JSON styles via GMSMapStyle (iOS) and MapStyleOptions (Android). Styles generated in Google Maps Platform Styling Wizard. Applied in one line:
mapView.mapStyle = try? GMSMapStyle(jsonString: mapStyleJSON)
Common Integration Issues
Android: ClassNotFoundException: com.google.android.gms.maps.MapFragment — outdated MapFragment instead of SupportMapFragment. In modern projects with Navigation Component always use SupportMapFragment.
iOS: crash GMSServices.provideAPIKey called twice — if both AppDelegate and SceneDelegate initialize SDK. Move initialization to one place.
Quotas: Google Maps free tier — 28,500 map loads per month. Each getMapAsync or GMSMapView creation — one load. If map recreates on every screen open (wrong lifecycle), quota melts fast.
Timeline
1–3 days. Basic map with markers — 1 day. Full integration with custom style, info windows and routes — 2–3 days. Cost calculated individually.







