Kahibaro
Discord Login Register

28.1 Google Maps Integration

Getting Access to Google Maps

Before you can show a Google Map inside your Android app, you must obtain permission to use the Maps SDK for Android from Google and link it to your project.

The first step is to create or use an existing Google account and sign in to the Google Cloud Console at https://console.cloud.google.com. In the console, you either create a new project for your app or select an existing one. This project represents your Android app and will hold your credentials and configuration.

Next, you enable the Maps SDK for Android API for that project. In the Cloud Console, you go to the APIs & Services section, find the Maps SDK for Android, and enable it. Without this step, even if your code is correct, the map will not function because the service is not activated for your project.

Finally, you create an API key. The API key is a string that identifies your app to Google services. In the Credentials section of the Cloud Console, you create a new API key. At first, you can test with an unrestricted key, but for real apps you should restrict it to your Android app by package name and SHA-1 certificate fingerprint. This prevents others from using your key.

Once you have generated the API key, you copy it, because you will add it to your Android project so that the Maps SDK can authenticate your requests.

Never commit your unrestricted API key to a public repository. Always restrict your key to your app and consider using different keys for debug and release builds.

Adding Maps Dependencies to Your Project

To use Google Maps in your Android project, you must add the proper library dependency using Gradle. In your app level build.gradle or build.gradle.kts file, you include the Play Services Maps library.

A typical dependency entry looks like this:

dependencies {
    implementation "com.google.android.gms:play-services-maps:18.2.0"
}

The exact version number may change over time. You should check the latest version in the official documentation or in Maven Central. After adding the dependency, you sync the Gradle project so the Maps library is available in your code.

This integration relies on Google Play services on the device. If Google Play services are not installed or not up to date, the map will not display correctly, and you must handle that case inside your app.

Configuring the Manifest and API Key

The Maps SDK needs to know your app has permission to access the internet and also needs to receive your API key. Both are provided using the manifest and your resource files.

First, you ensure you have the correct permissions in AndroidManifest.xml so the map tiles and related data can be downloaded.

At minimum you add:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

For basic map display these are sufficient. Location related permissions belong to location chapters, so you do not add them here unless you already implement location features.

The API key is usually placed as a string resource and referenced through a meta data entry in the application tag. In your res/values/google_maps_api.xml file, you might have:

<resources>
    <string name="google_maps_key" translatable="false">
        YOUR_API_KEY_HERE
    </string>
</resources>

Then in your AndroidManifest.xml, inside the <application> tag, you add:

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="@string/google_maps_key" />

The Maps SDK reads this meta data when your application starts and uses the API key for all map requests.

Using the Google Maps Activity Template

Android Studio offers a Google Maps Activity template that creates a working map screen with minimal effort. This is the easiest way to get started.

When you create a new Activity, you can choose the Google Maps Activity template. Android Studio then generates:

A layout file that includes a SupportMapFragment.

An Activity class that implements the OnMapReadyCallback interface.

A resource file google_maps_api.xml with comments showing where to place your API key.

The generated code typically looks like this for the Activity:

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var mMap: GoogleMap
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        // You can customize the map here
    }
}

This template hides some of the boilerplate and lets you focus on customizing the map once it is ready. You can then add your own logic inside onMapReady.

Adding a Map Fragment Manually

If you do not want to use the template, you can integrate Google Maps manually into an existing Activity using a SupportMapFragment.

In your layout XML file, you declare the fragment for the map.

<fragment
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

This fragment reserves space in your UI for the map. In your Activity, you retrieve this fragment and register an asynchronous callback.

class MyMapActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var map: GoogleMap
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_map)
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }
    override fun onMapReady(googleMap: GoogleMap) {
        map = googleMap
        // Customize the map
    }
}

The getMapAsync method triggers the process of connecting to the Google Play services and preparing the map. Only when onMapReady is called can you safely interact with the GoogleMap object. Before that moment, the map is not ready, and calls on it will fail or be ignored.

Always interact with the GoogleMap object after onMapReady has been called. Any attempt to use it before this callback risks crashes or undefined behavior.

Basic Map Types and UI Controls

Once you have a GoogleMap instance in onMapReady, you can change how the map looks and what controls it displays.

Maps support several types. The most common ones are:

MAP_TYPE_NORMAL for the standard road map.

MAP_TYPE_SATELLITE for satellite imagery.

MAP_TYPE_TERRAIN for physical landscape data.

MAP_TYPE_HYBRID for satellite imagery with road labels.

You can set the type like this:

override fun onMapReady(googleMap: GoogleMap) {
    map = googleMap
    map.mapType = GoogleMap.MAP_TYPE_NORMAL
}

You can also enable or disable user interface controls that Google provides. For example, zoom controls or the compass.

map.uiSettings.isZoomControlsEnabled = true
map.uiSettings.isCompassEnabled = true
map.uiSettings.isMapToolbarEnabled = true

These settings control default UI elements that are drawn on top of the map and help the user navigate or interact with it without you having to implement such controls manually.

Moving the Camera and Setting Initial Position

By default, the map might start somewhere that is not useful for your app. You usually want to show a specific area when the Activity starts.

The visible region of the map is controlled by the camera. You can set its position using a LatLng object for the geographic coordinates and a zoom level. The zoom is a float value where higher numbers zoom in more. Typical values range from about 2 for a full world view to 20 for street level buildings.

For example, to focus on a city:

override fun onMapReady(googleMap: GoogleMap) {
    map = googleMap
    val sydney = LatLng(-34.0, 151.0)
    val zoomLevel = 10f
    map.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, zoomLevel))
}

moveCamera instantly changes the view. If you prefer a smooth animation, you can use:

map.animateCamera(CameraUpdateFactory.newLatLngZoom(sydney, zoomLevel))

You can later move the camera again in response to user actions or other events in your app. The camera is central to how users perceive the map, so choosing a good initial position and zoom is important for usability.

Adding Markers to the Map

Markers represent points of interest on the map, such as locations, stores, events, or any specific coordinates you want to highlight. A marker appears as a pin icon that the user can tap to see more information.

To add a marker, you create a MarkerOptions object, set its position and optionally a title and a snippet, then add it to the map.

override fun onMapReady(googleMap: GoogleMap) {
    map = googleMap
    val location = LatLng(37.4219999, -122.0840575)
    val markerOptions = MarkerOptions()
        .position(location)
        .title("Example Place")
        .snippet("This is a marker near a famous location")
    map.addMarker(markerOptions)
    map.moveCamera(CameraUpdateFactory.newLatLngZoom(location, 15f))
}

The title appears as the primary text in the info window that opens when the user taps the marker. The snippet is optional secondary text.

Markers are lightweight objects, but you should avoid adding huge numbers at once on low end devices. For large data sets you usually use clustering, which is an advanced topic.

You can keep a reference to the Marker returned by addMarker if you want to change or remove it later.

A Marker belongs to the map that created it. Do not reuse a Marker on a different GoogleMap instance. Always create new markers when you need them on another map.

Handling Marker Clicks and Map Gestures

Maps are interactive surfaces. The user can pan, zoom, and tap. You can respond to some of these interactions to provide a richer experience.

To detect when a user taps a marker, you implement a marker click listener.

map.setOnMarkerClickListener { marker ->
    // Handle marker click
    // Return true if you consume the event, false to let default behavior occur
    false
}

If you return false, the map will still show the default info window for that marker. If you return true, the default behavior is suppressed, so you can control the interaction fully.

Similarly, if you want to know when the user taps on the map background, not on a marker, you can set a map click listener.

map.setOnMapClickListener { latLng ->
    // User tapped on the map at latLng
}

You can also listen for long clicks:

map.setOnMapLongClickListener { latLng ->
    // User long pressed on the map at latLng
}

Map gestures like zoom and pan are usually handled automatically by the map UI. However, if needed, you can disable user gestures or specific ones through uiSettings, for example to create a static map view.

Customizing Marker Icons and Info Windows

The default marker icon is a red pin. You can change the appearance to match your app design or to represent different categories of locations.

To use a custom icon, you supply a BitmapDescriptor when creating MarkerOptions.

val customIcon = BitmapDescriptorFactory.fromResource(R.drawable.my_marker_icon)
val markerOptions = MarkerOptions()
    .position(location)
    .title("Custom Marker")
    .icon(customIcon)
map.addMarker(markerOptions)

For better visual quality, you should provide properly scaled images and consider the density of the device.

If you want to customize the contents or the layout of the marker info window that appears when the user taps a marker, you implement a InfoWindowAdapter on the map. This adapter allows you to provide a custom view for the info window.

For example:

map.setInfoWindowAdapter(object : GoogleMap.InfoWindowAdapter {
    override fun getInfoWindow(marker: Marker): View? {
        // Return null to use default frame
        return null
    }
    override fun getInfoContents(marker: Marker): View? {
        val view = layoutInflater.inflate(R.layout.custom_info_window, null)
        // Fill your view with marker data here
        return view
    }
})

Returning null from getInfoWindow but a view from getInfoContents keeps the standard info window frame but changes the content inside it. With this approach, you can show more detailed information or a layout that better fits your app.

Map Styling and Theming

You can customize the visual style of the entire map to match your app's branding or improve readability in certain scenarios, for example for dark mode.

Map styling uses a JSON definition that describes the appearance of roads, labels, water areas, and other map features. You can generate this JSON within the Google Maps styling wizard in the documentation and then place the file in your resources, commonly as res/raw/map_style.json.

To apply a style, you load it and set it on the map in onMapReady.

override fun onMapReady(googleMap: GoogleMap) {
    map = googleMap
    val success = map.setMapStyle(
        MapStyleOptions.loadRawResourceStyle(
            this,
            R.raw.map_style
        )
    )
    if (!success) {
        // Handle style parsing errors
    }
}

By choosing or creating an appropriate style file, you can reduce visual clutter, highlight specific regions, or provide a darker map surface that integrates better with a dark themed app.

Checking and Handling Google Play Services Availability

The Maps SDK depends on Google Play services on the device. A user may have an outdated or missing version of Google Play services, which can prevent the map from working correctly.

To handle this, you can use GoogleApiAvailability to check the status before using the map. If there is a problem, you can show a dialog that guides the user to update Google Play services.

The typical pattern is:

val googleApiAvailability = GoogleApiAvailability.getInstance()
val status = googleApiAvailability.isGooglePlayServicesAvailable(this)
if (status != ConnectionResult.SUCCESS) {
    if (googleApiAvailability.isUserResolvableError(status)) {
        googleApiAvailability.getErrorDialog(this, status, 9001)?.show()
    } else {
        // Device is not supported
    }
} else {
    // Safe to use Maps
}

This check is important for apps distributed to a wide variety of Android devices, especially in regions where Google Play services might not be present or fully up to date.

Common Integration Issues and Troubleshooting

When integrating Google Maps, many problems are misconfigurations of the API key or project settings, rather than coding errors.

If the map area shows only a gray grid or an error message, these are typical checks:

Verify that the Maps SDK for Android is enabled in the Google Cloud Console for the correct project.

Ensure that the API key in your app matches the key shown in the console and that the package name and SHA 1 certificate restrictions, if any, match your app configuration.

Check that the device is connected to the internet and that the required permissions for network access are present in the manifest.

Ensure that Google Play services on the device are installed and updated.

Error logs in Logcat often contain informative messages from the Maps SDK, such as missing or invalid API keys. Carefully reading these logs can guide you to the misconfigured part.

By paying attention to these common issues during integration, you can reduce the time spent debugging and achieve a functioning map earlier in development.

Views: 5

Comments

Please login to add a comment.

Don't have an account? Register now!