Google map, location service, and Firebase Realtime Database on Android — Basic
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 thedependencies
element underbuildscript
.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 theplugins
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. ReplaceYOUR_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 tocom.google.android.geo.API_KEY
and update theandroid: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, mMap
true 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.