Chapters

Hide chapters

Android Apprentice

Third Edition · Android 10 · Kotlin 1.3 · Android Studio 3.6

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section III: Creating Map-Based Apps

Section 3: 7 chapters
Show chapters Hide chapters

13. Creating a Map-Based App
Written by Namrata Bandekar

Have you ever been on a road trip and wanted to make notes about the places you’ve visited; needed to warn your future self about some heartburn-inducing greasy food from a roadside diner; or you wanted to keep reminders about the best menu items at your favorite local restaurants?

If you answered “yes” to any of those questions, then you’re in luck! You’re about to build PlaceBook, an app that meets all of those needs by letting you bookmark and make notes using a map-based interface.

Getting started

While building PlaceBook, you’ll use familiar techniques from the previous sections and learn about several new Android APIs. Along the way, you’ll use:

  • Google Maps API to display a map, track the user’s location and add custom markers.
  • Google Places API to display place information and search for nearby places.
  • Room Persistence Library to store data.

You’ll also learn about Implicit Intents for sharing your data to other apps.

There’s a lot of ground to cover, but in the end, the final product will look like this:

About PlaceBook

PlaceBook starts by displaying a Google Map centered around your current location. The map will display common places, and allow you to bookmark them. You can display details for bookmarked places and edit the place data and corresponding photo.

The navigation drawer on the left will display all of your bookmarks, and tapping on one will zoom the map to that location. You can use the search icon to find nearby places and jump directly to them on the map.

Making a plan

With a large number of features to implement, it’s best to think about them in bite-sized pieces. From there, you can slowly build up to the finished product. The steps you’ll take to accomplish this are as follows:

  1. First, you’ll create a basic map to display the user’s current location. You’ll get familiar with the Google Maps API and the Fused Location Provider.

  2. You’ll then allow the user to select Places on the map and display information about the place. You’ll learn how to load detailed information about a Place using the Google Places API.

  3. You’ll add the basic bookmarking ability by using Room to store places in a local database and add map markers to show the user’s bookmarked locations.

  4. Next, you’ll add a Details screen to let the user edit their bookmark details, delete bookmarks and replace the default bookmark photo with one from the camera or photo gallery.

  5. You’ll add a navigation drawer to let the user jump directly to any saved bookmark.

  6. You’ll then use the Google Places autocomplete service to let the user search for nearby locations.

  7. You’ll add the ability to long tap any location on the map to add a bookmark that doesn’t already have an existing place on the map.

  8. Finally, you’ll add some finishing touches to make the app look better with a custom color theme and icons.

Location service components

The Android SDK provides three main components related to location and mapping:

  • Framework Location APIs: Known collectively as the location framework, this framework has been around the longest and is the traditional means for getting the user’s current location. However, it’s also the most difficult to use.

  • Google Maps API: The Google Maps API makes it easy to display interactive maps within your app. It provides a lot of functionality out-of-the-box, including everything needed to display detailed map data and respond to user gestures. You’ll cover this API in detail in the next chapter.

  • Google Play Services location APIs: The Google Play services location APIs are built on top of the Core Location framework and alleviate much of the pain involved with tracking a user’s location. You’ll be using the FusedLocationProviderApi component of this API in the book.

Map wizard walk-through

To save time, you’ll use the Maps Activity project template to generate an app with a single Activity that displays a map.

To begin, launch Android Studio and select Start a new Android Studio Project.

Select Google Maps Activity under Phone and Tablet. You’ll find this on the Choose your project dialog. Once selected, click Next.

Fill out the Configure your project dialog with the information below:

  • Name: PlaceBook
  • Package name: com.raywenderlich.placebook
  • Save location: select a directory for the project files
  • Language: Kotlin
  • Minimum API level: API 21
  • Leave everything else unchecked.

Click Finish.

If all goes as planned, Android Studio automagically generates your new project and performs an initial build.

Google Maps API key

Before your app will work, you need to generate an API key using the free Google Developer Console, which requires a Google account.

The Google Maps API communicates with the Google Map servers and only works if a valid key is provided by the app. Open the google_maps_api.xml file. Android Studio generates this file to make things easier. It also provides important information to help you create the Google Maps API key.

The easiest way to create an API key is to use the link at the top of google_maps_api.xml, shown here and highlighted in yellow:

Take note of the Package Name and SHA-1 Fingerprint values. These are the two requirements for generating a key. The link is just an easy way to pass those values to the key generation page in the Google Developer Console.

Package Name is straightforward: It’s the package name you used when creating the project. The SHA-1 Fingerprint may look a little odd if you’re not familiar with SHA-1. SHA-1 is a method for generating secure hashes. Just like a real fingerprint uniquely identifies an individual, each SHA-1 fingerprint uniquely identifies a set of bytes.

The fingerprint in google_maps_api.xml is an SHA-1 hash of the certificate from your debug keystore file. A keystore file contains everything you need to digitally sign an Android application (APK) file. During development, your apps are signed with a debug keystore file. When delivering apps to the Play Store, you sign with a release keystore file.

The debug keystore file is automatically generated when you first install Android Studio and is shared among all of your projects. Using a release keystore is covered in detail in Chapter 30, “Preparing for Release”.

If you’ve worked with Google Maps before, you may have already generated a Google Maps API key. You can add the Package Name and SHA-1 Fingerprint to an existing key instead of generating a new one.

There are actually two versions of google_maps_api.xml in your project. One version is used only when building the debug version, while the other is used only for the release version.

If you’re using the Android View in the Project View, you’ll only see one version of the file in the app/res/values folder; however, you’ll see (debug) after the filename.

To see both versions, switch over to the Project Files view by selecting the Project Files tab in the Project View window. Open the app/app/src/debug/res/values and app/app/src/release/res/values folders and you’ll notice that there’s a google_maps_api.xml file in each one. By placing files in these build-specific folders, Android Studio can apply them separately to debug or release builds as appropriate.

Follow the link provided in google_maps_api.xml and you’ll see the following page after signing in to your Google account.

Click Agree and continue, and you’ll come to a page displaying The API is enabled.

This created a project behind the scenes in your Google Developer console and enabled the Maps SDK for you. In a later chapter, you’ll learn how to manually enable APIs. For now, just remember which Google account you created this project with so you can edit it later.

Click Create API key and you’ll see the APIs & Services dashboard containing your shiny new key under the API Keys section:

Copy the key and paste it into google_maps_api.xml where it reads YOUR_KEY_HERE. The resulting file will look something like this, but with your key instead:

<string name="google_maps_key"
        templateMergeStrategy="preserve"
        translatable="false">
        AIza5sD-_G2dq7PjafW-Ad4pKpU5a</string>

Getting the keystore fingerprint

Although Android Studio conveniently placed your debug keystore fingerprint in the XML file, it’s helpful to know how to get the fingerprint yourself should you ever need to regenerate it. The following instructions work for debug builds; getting the SHA1 key for release builds is covered in Section VI, “Submitting Your App”.

First, locate your keystore file.

  • On macOS, the keystore file is located in ~/.android/.
  • On Windows, you’ll find the keystore file in C:\Users\your_user_name\.android\.

On macOS, run the following command:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

On Windows, run the following command:

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android.

This produces output similar to this:

Alias name: androiddebugkey
Creation date: Jan 01, 2013
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 4aa9b300
Valid from: Mon Jan 01 08:04:04 UTC 2013 until: Mon Jan 01 18:04:04 PST 2033
Certificate fingerprints:
     MD5:  18:5E:95:D0:A6:86:89:BC:A8:70:BA:34:FF:6A:AC:A4
     SHA1: A5:1F:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:46
     Signature algorithm name: SHA1withRSA
     Version: 3

The SHA1 key you see will match what’s already in the XML file.

Maps and the emulator

If you’re installing on a device, that’s all you need. However, if you’re using an emulator, then things can get a little more complicated.

A basic requirement of the Google Maps API is that your device must have the Google APIs installed. Not all emulators include this by default. If you don’t have one already, use the following steps to create an emulator with API Level 19 or newer that includes the Google APIs.

Select Tools ▸ SDK Manager. Under the SDK Platforms tab, select Show Package Details.

Select a version from Android with API level 21 or newer. Make sure Android SDK Platform and Google APIs Intel x86 Atom_64 System Image are selected. If Google APIs is an option, select it as well. The following shows Android 10.0 (Q) with the necessary items selected.

Click OK to install the platform files.

Once the installation is complete, select Tools ▸ AVD Manager, and then click Create Virtual Device.

Select your preferred device and click Next. For demonstration purposes, the following screen shot shows an emulator set up for a Pixel 2 device.

The Recommended tab displays a choice that matches the SDK platform files you downloaded in the previous step. Make sure you select the one with the Google Play option. If you don’t see one on the Recommended tab, then try the x86 images tab.

Click Next.

On the Configuration screen, leave the default settings as-is and click Finish.

You’ll see the new virtual device shown along with any others you may have created before. Make sure to use this virtual device when launching the app.

Running the app

Launch the app from Android Studio.

If your key is valid, you’ll see a map on the screen. If you see a blank screen, check Logcat for error messages.

If you see an error message in Logcat that looks like the one shown here, double check that you pasted the correct key in google_maps_api.xml:

Google Maps Android API: Authorization failure.  Please see https://developers.google.com/maps/documentation/android-api/start for how to correctly set up the map.
Google Maps Android API: In the Google Developer Console (https://console.developers.google.com)
Ensure that the "Google Maps Android API v2" is enabled.
Ensure that the following Android Key exists:                                                                                        API Key: YOUR_KEY_HERE
    Android Application (<cert_fingerprint>;<package_name>): 6A:27:6F:34:38:DA:D3:04:C8:9C:8F:41:ED:BB:B7:18:02:77:67:D2;com.raywenderlich.placebook

Look at the key shown after API Key: in Logcat and ensure that it matches the one you received when you created your key.

Once you have the correct key, you’ll see a map with a marker placed over Sydney, Australia.

Congratulations! You’re off to a great start with your maps app. Pan and zoom around a bit. There’s not much more you can do at this point but that will change soon enough!

Before moving on, take a moment to review the files Android Studio created for you.

Project dependencies

Before you can use maps in your app, you have to add the two required dependencies. To find the first one, open build.gradle from your application module folder. In the dependencies section, you’ll see the following line:

implementation 'com.google.android.gms:play-services-maps:16.0.0'

This instructs the Gradle build system to include the Maps API in your build and is required to use maps.

You may be wondering, “How do I know which version of the library to include?”

Good question! There are at least three ways to find the latest version:

  1. Go to https://developers.google.com/android/guides/setup. Scroll down to see the list of APIs. This list is dynamically generated and reflects the most recent version of each API.

  1. Go to https://developers.google.com/maps/documentation/android-sdk/releases. Note the latest release of Maps SDK for Android.

  1. Select File ▸ Project Structure. Select the Dependencies tab, then select app under Modules. Click +, and select 1 Library Dependency.

Type play-services-maps and press Enter.

You’ll see the latest available version:

Note: In Google Play services versions prior to 6.5, all of the Play services were included in one package named play-services. This would often lead to problems with creating APK files that exceeded the 65 KB method limit.

Now, you can choose only the subset of play services required for your app, such as the Google Maps API.

The manifest

First, from app/manifests, open AndroidManifest.xml. It’ll look like the following, with your API key displayed in place of @string/google_maps_key:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.raywenderlich.placebook">

  <!--
       The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
       Google Maps Android API v2, but you must specify either coarse or fine
       location permissions for the 'MyLocation' functionality.
  -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

  <application
      android:allowBackup="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/AppTheme">

    <!--
         The API key for Google Maps-based APIs is defined as a string resource.
         (See the file "res/values/google_maps_api.xml").
         Note that the API key is linked to the encryption key used to sign the APK.
         You need a different API key for each encryption key, including the release key that is used to
         sign the APK for publishing.
         You can define the keys for the debug and release targets in src/debug/ and src/release/.
    -->
    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="@string/google_maps_key" />

    <activity
        android:name=".MapsActivity"
        android:label="@string/title_activity_maps">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>

</manifest>

This is a fairly standard manifest file, and most of it should look familiar from previous sections. Look at the uses-permission element. As the comment in the file indicates, the ACCESS_FINE_LOCATION permission is not required to show the map. You could remove this line, and your app would continue to run fine, but you’re going to need this later. In the next chapter, you’ll cover permissions in detail and discover why this permission is needed when obtaining the user’s location.

The meta-data tag under the Application section is where Android Studio looks for your API key when signing the APK. From the raw source shown above, you can see the key is pulling from the string resource you defined in google_maps_api.xml. When viewing the file in Android Studio, it’ll show you the key.

The activity and layout

Open MapsActivity.kt. This is the startup Activity created from the Maps template. Note that it inherits from AppCompatActivity and the OnMapReadyCallback interface.

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

Map display options

There are two ways to display a map in your app:

  1. As a fragment using the SupportMapFragment class: SupportMapFragment is a subclass of Fragment and is the typical choice unless you need fine-grained control of the map. You can also use MapFragment, but using SupportMapFragment provides the best support for backwards compatibility.

    Remember how you used fragments to host the main UI in the ListMaker app? The MapsActivity template does the same thing by hosting the SupportMapFragment within your Main Activity.

    SupportMapFragment acts as a reusable component that you can easily plug into any Activity. It handles all aspects of displaying the map and gives you access to the GoogleMap object.

  2. As a view using the MapView class: MapView is a subclass of View and can be used in two modes: Fully Interactive Mode or Lite Mode. You can place MapView directly inside your own fragment or Activity. When using this in fully interactive mode, you’re responsible for forwarding lifecycle methods to the MapView. In lite mode, forwarding the lifecycle events is optional.

The template uses the MapFragment option. Look at onCreate() in MapsActivity:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_maps)
  // Obtain the SupportMapFragment and get notified when the map is ready to be used.
  val mapFragment = supportFragmentManager
      .findFragmentById(R.id.map) as SupportMapFragment
  mapFragment.getMapAsync(this)
}

It loads the activity_maps.xml Layout, then it finds the map Fragment from the Layout and uses it to initialize the map using getMapAsync().

activity_maps.xml contains nothing but a container for the SupportMapFragment mentioned earlier.

<fragment
    android:id="@+id/map"
    android:name=
        "com.google.android.gms.maps.SupportMapFragment"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.raywenderlich.placebook.MapsActivity"/>

Asynchronous map setup

When you call getMapAsync(), the SupportMapFragment object handles all of the work of setting up the map and creating a GoogleMap object. The GoogleMap object is what you’ll use to control and query the map.

If you’re familiar with the concept of asynchronous methods, you may have guessed from the name that getMapAsync() is asynchronous. Unlike a normal or synchronous method, which does its work then returns to the caller, an asynchronous method starts up a different thread to do its work and doesn’t return immediately to the caller. The code that calls the asynchronous method goes on its merry way while the real work is done behind the scenes.

While getMapAsync() is doing its background work, you should not try to interact with the map. So how will you know when the map is ready? That’s where OnMapReady() comes to the rescue!

Look at OnMapReady():

override fun onMapReady(googleMap: GoogleMap) {
  mMap = googleMap

  // Add a marker in Sydney and move the camera
  val sydney = LatLng(-34.0, 151.0)
  mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
  mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
}

The override keyword on onMapReady() lets you know that this is overriding a method from the base class or an interface. In this case, onMapReady() is part of the OnMapReadyCallback interface included in the class declaration. OnMapReady() is called by the SupportMapFragment object when the map is ready to go. You passed in a GoogleMap object that’s then used to interact with the map.

Note: It’s possible that the device running your app won’t have the Google Play services installed. If that’s the case, the SupportMapFragment object prompts the user to install the Google Play services. getMapAsync() will not call onMapReady() until the services are installed.

The GoogleMap object is stored away in the mMap local variable, and then some methods are used to add a marker and zoom the map to it. For now, don’t worry about how the methods work; you’ll cover those in detail in upcoming chapters.

Note: You might be wondering why the GoogleMap object is being held in a variable named mMap. It may seem like a typo; however, Android Studio generates code using what’s known as Hungarian Notation. It was developed during a time where advanced development environments like Android Studio didn’t exist. The notation was used as a way for developers to easily identify if a variable was a class property, or a local variable, or a static variable.

Now, development environments use colors to help you identify variables and their scopes. You can read all about it at https://en.wikipedia.org/wiki/Hungarian_notation. For the purposes of this book, you’ll just use sensible naming, so go ahead and rename mMap to map.

The difficulty of determining locations

Determining a user’s location is a rather involved process under the hood. There are multiple sources of location data to handle, and they all affect your device’s idea of where it is in the world.

Some of the challenges in locating the user’s location include:

  • Dealing with multiple methods for determining location: Your mobile device has several ways to determine your location, and each has its own benefits and disadvantages. Your phone uses the GPS chip, Wi-Fi location and cell towers to zero in on your location. You have to decide which one to use to balance desired accuracy with power consumption.
  • Tracking change in user location: As the user moves around, you have to know when to update the location to reflect the current position.
  • Handling different accuracy levels: Each location source offers different levels of accuracy and can vary at any time. In some cases, an older location has better accuracy than the most recent location.

Android uses Location Providers to provide access to the different location sources mentioned above. When you need the user’s location, you decide which set of location providers to use and instruct them to start listening for location updates through the location manager.

A typical flow to get the user’s location might look like this:

  1. At some point after the application starts, begin listening for updates from the chosen location providers.
  2. Implement logic to filter out updates and select the most appropriate ones. Remember that newer locations are not always the best.
  3. Stop listening when you’re done to preserve power.
  4. Make use of the best location in your app logic.

The following graphic illustrates the location signals that your app might receive as it goes along. Note that the graph shows a time sequence of location events.

Location events over time
Location events over time

Note: Image credit Android Open Source Project, used according to terms described in the Creative Commons 2.5 Attribution License. The original image appears in Location Strategies.

There are many decisions to make to determine how to best calculate the user’s location:

  • Determine precisely when to start listening for updates: You may want to start listening before the location is needed, so the user doesn’t perceive a delay.
  • Determine the filter criteria for weeding out locations based on their accuracy and time received: Do you want the quickest locations? The most accurate? Or some combination of the two?
  • Determine how long to listen to balance power efficiency: On a mobile device, battery is a precious resource. Keeping the location provider running will drain this resource faster than just about anything else on the device.

Where to go from here?

As you can see, there are many moving parts, and there is a lot of code required to provide a seamless experience to the user. Thankfully, the location APIs are there to do the heavy lifting for you.

Although you will continue to extend this app over the next few chapters, you can access more extensive information on the developer pages for the Google Places SDK for Android.

That’s it for this chapter! In the next chapter, you’ll get your first look at customizing the map behavior with location tracking and markers.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.