Google map, location service, and Firebase Realtime Database on Android — Basic

Kai Xie
9 min readAug 22, 2021

--

Map and location service are very important features on mobile devices, and they are one of the major distinctions compared to traditional desktop systems. With map and location service, the users could get where they are, show their location on the map, and acquire the local stores and services nearby.

So this article will address how to implement a simple Android App with map and location service.

Integrate Google Maps

The first thing we need to do is to create an Android app that displays a map by using the Google Maps template for Android Studio

Create a new project based on Google Maps Activity template

We can follow Maps SDK for Android Quickstart to create the project. This step is pretty straightforward.

The only thing we need to pay attention to is this project needs to be with Google Maps Activity template as follows:

and then complete the Google Maps Activity form like

And then the Android Studio would create the project to display the Google Map for you.

Set up in Cloud Console

After you create the Android project, you would also need to Set up in Cloud Console to use the Google Maps Platform APIs.

You would need to create a new project, enable the billing, enable the API. Just follow the instructions step by step, and you will get an API key, which is the credential between your App and the Google Map Platform.

Then, let’s go back to our project and follow the steps to add the API Key to the App.

In Android Studio, open your root-level build.gradle file and add the following code to the dependencies element under buildscript.

buildscript {
dependencies {
// …
classpath “com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.0”
}
}

Next, open your app-level build.gradle file and add the following code to the plugins element.

id ‘com.google.android.libraries.mapsplatform.secrets-gradle-plugin’

Save the file and sync your project with Gradle.

Open the local.properties in your project level directory, and then add the following code. Replace YOUR_API_KEY with your API key.

MAPS_API_KEY=YOUR_API_KEY

Save the file and sync your project with Gradle.

In your AndroidManifest.xml file, go to com.google.android.geo.API_KEY and update the android:value attribute as follows:

<meta-data
android:name=”com.google.android.geo.API_KEY”
android:value=”${MAPS_API_KEY}” />

And then build the App and run it on the device or emulator with Google Service (mandatory), you would see the App is showing a map like

And you would see a marker in Sydney as well. So let’s check the code.

Add Controls

You may also notice this map is not good enough because it doesn’t support zoom, and it doesn’t show the compass either. So let’s add some controls to it.

We need to open src/main/res/layout/activity_maps.xml file and add the following lines in bold

<?xml version="1.0" encoding="utf-8"?>
<fragment 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:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MapsActivity"
map:cameraTilt="30"
map:cameraZoom="13"
map:mapType="normal"
map:uiCompass="true"
map:uiRotateGestures="true"
map:uiScrollGestures="true"
map:uiTiltGestures="true"
map:uiZoomControls="true"
map:uiZoomGestures="true"
/>

Then let’s build and run the App again, it would show the map like

That looks better. You should be able to move the map by dragging or zoom the map by clicking the zoom button.

There is a trivial thing left. You might notice the name of this App displayed on the launcher is MapActivity, which is not good enough. So let’s update it by open src/main/res/values/strings.xml and update the value of title_activity_maps to any text you want to see on the launcher.

We have already had a simple App to show the map, and we would implement the most important feature for the Map App: where am I. We need to integrate location service to reach this goal.

Integrate Location Service

We will focus on the location service with FusedLocationProviderClient in this chapter.

FusedLocationProviderClient is the main entry point for interacting with the fused location provider. We can get the current location with this interface.

Request location permission

Your app must request location permission in order to determine the location of the device and to allow the user to tap the My Location button on the map.

For more details, see the guide to Android permissions.

Firstly, we need to add the permission as a child of the <manifest> element in your Android manifest if it is not there as

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="...">
...
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>

Secondly, we also need to request runtime permissions in your app, giving the user the opportunity to allow or deny location permission. The following code checks whether the user has been granted fine location permission. If not, it requests the permission:

@SuppressLint("MissingPermission")
private fun getLocationPermission() {
/*
* Request location permission, so that we can get the location of the
* device. The result of the permission request is handled by a callback,
* onRequestPermissionsResult.
*/
if (ContextCompat.checkSelfPermission(this.applicationContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mMap.isMyLocationEnabled = true
} else {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST_CODE)
}
}

And also need to add the interface OnRequestPermissionsResultCallback to the MapsActivity by updating the signature of MapsActivity to

class MapsActivity : AppCompatActivity(), OnMapReadyCallback, OnRequestPermissionsResultCallback {

and import the OnRequestPermissionsResultCallback as requested.

Then we also need to implement the API onRequestPermissionsResult with following code

@SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) {
return
}
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
mMap.isMyLocationEnabled = true
} else {
// Permission was denied. Display an error message
// Display the missing permission error dialog when the fragments resume.
mMap.isMyLocationEnabled = false
}
}

And we need to add the const value to this class with the following code

companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1
}

And then we need to call getLocationPermission in the Map app lifecycle function onMapReady.

This part is the standard approach to request runtime permission on Android 23 and onward. See more details at https://developer.android.com/training/permissions/requesting

Briefly speaking, we need to check whether the permission ACCESS_FINE_LOCATION has already been granted. If not, call ActivityCompat.requestPermissions to request the permission, which would trigger a permission popup to ask the user to grant permission. If the user clicks Allow or Deny button on the popup, the callback function onRequestPermissionsResult would be triggered and then we can handle the ‘allow’ case and ‘deny’ case differently. In our app, we just set the isMyLocationEnabled property of our map instance, mMaptrue or false.

And we also need to add a global permission request code to indicate on which permission request popup the user clicked. And we need only one code, LOCATION_PERMISSION_REQUEST_CODE, because we are requesting only one permission.

And then let’s tun the App again. You would see the permission request popup at the very beginning like

Then you can grant the ACCESS_FINE_LOCATION by clicking ‘While using the app’ or ‘Only this time’, or reject it by clicking ‘Deny’, which means you are unable to get the location of the device.

You can change the permission in device’ Setting / Apps / Permission Manager / Location / <App> like

Position the map

We have got the location of the device, so we are able to position the map to the current location.

Firstly we need to add the following dependency into build.gradle(module)

implementation 'com.google.android.libraries.places:places:2.4.0'

and create an instance of FusedLocationProviderClient in MapActivity like

private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

and instantiate it in the onCreate function as followed

fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

and add a new function getDeviceLocation as followed

@SuppressLint("MissingPermission")
private fun getDeviceLocation() {
/*
* Get the best and most recent location of the device, which may be null in rare
* cases when a location is not available.
*/
try {
if (mMap.isMyLocationEnabled) {
val locationResult = fusedLocationProviderClient.lastLocation
locationResult.addOnCompleteListener(this) { task ->
if
(task.isSuccessful) {
// Set the map's camera position to the current location of the device.
lastKnownLocation = task.result
if (lastKnownLocation != null) {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
LatLng(lastKnownLocation!!.latitude,
lastKnownLocation!!.longitude), DEFAULT_ZOOM.toFloat()))
}
} else {
mMap.moveCamera(CameraUpdateFactory
.newLatLngZoom(defaultLocation, DEFAULT_ZOOM.toFloat()))
mMap.uiSettings.isMyLocationButtonEnabled = false
}
}
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}

In this function, we will check the isMyLocationEnabled, if it is true, it means the location service is enabled, so we can get the last known location by calling fusedLocationProviderClient.lastLocation, and save the location to the variable lastKnownLocation, and it is not null, then move the camera to this location. or else, we just move the camera to a default location if the location service is disabled.

You might notice there are several missing symbols in this piece of code. So let’s fix this issue by adding properties of MapsActivity like

private var lastKnownLocation: Location? = null
private val defaultLocation
= LatLng(-33.8523341, 151.2106085)

and update the companion object block as followed

companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1
private const val DEFAULT_ZOOM = 15
}

You might also notice we disable the location button by

mMap.uiSettings.isMyLocationButtonEnabled = false

if the location service is disabled.

And we also call getDeviceLocation just after calling getLocationPermission inside the onMapReady function.

And let’s run the app, you would be able to move the camera by clicking the location button at the top-right

and you could also test it with an Android emulator by changing the location in Extended Controls as

If we reject the permission to the location service, we would NOT see the location button on the map like

And the map is showing the hardcoded default location.

Show the last location

The users might also want to display the last visited location when they launch the app, so we need to save the last visited location. So we need to add two following const properties

private const val KEY_CAMERA_POSITION = "camera_position"
private const val KEY_LOCATION
= "location"

to the companion object block of the MapsActivity. Actually part of saving the last visited location, we also want to save the camera position.

and implement the lifecycle function onSaveInstanceState to save the location and camera position in the state bundle as

override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(KEY_CAMERA_POSITION, mMap.cameraPosition)
outState.putParcelable(KEY_LOCATION, lastKnownLocation)
super.onSaveInstanceState(outState)
}

so these values can be used when the app is re-launched by adding the following code in the function onCreate as

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (savedInstanceState != null) {
lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);
cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
}

binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
...
}

And we also need to update the function onMapReady as followed

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

getLocationPermission()
getDeviceLocation()
lastKnownLocation?.apply {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
LatLng(latitude, longitude), DEFAULT_ZOOM.toFloat()))
}
}

It is pretty straightforward that the camera will be moved to the saved location instead of the hardcoded location when the app is launched.

And then if you run the app, you would find the location that is the last known.

Display marker

We will add a new feature to this map app in this chapter: allow users to add notes on the map. We will implement this feature with the marker function on the Google Map SDK.

We can add a very simple marker on the map with only one line of code first. Let’s add

mMap.addMarker(MarkerOptions().position(defaultLocation).title("Marker in Sydney"))

into the onMapReady function and run the app, you would see the marker on the map as follows

If you didn’t see it, you might need to zoom out or move the map because this marker is positioned in Sydney. And absolutely, you are able to put the marker at the location as you want.

So far, we have completed a basic map app, which displays the Google Map with standard marker, and also supports the location service to get the current location of the device. And we would explore more advanced features in the next article, such as how to customize the marker as a note and to share the note across all users.

Welcome to read the next article

to explore some advanced features.

--

--