Share
Explore

Android Location Services and Maps Integration

Location Services
· Get current location of device using FusedLocationProvider
· Track device’s location as it moves using FusedLocationProvider
Maps Integration
· Add a map to display a location
· Display user location on the map (blinking blue icon)
Add markers to the map // Annotations

megaphone

How we simulate location and moving around when using an Android Virtual Device

Simulating location and movement in an Android Virtual Device (AVD) is an essential skill for testing location-based applications like our geofencing demo.
megaphone

NMEA stands for National Marine Electronics Association. It's a standard format for GPS data that is widely used in marine navigation and other applications. NMEA messages contain information about location, speed, time, and other data related to GPS positioning.

In the context of Android emulators, you can use NMEA data to simulate the GPS location of the device. This is done by sending NMEA sentences to the emulator through tools like the Android Debug Bridge (ADB). The emulator interprets this data as if it were coming from a real GPS receiver, allowing you to test your location-based applications without the need for a physical device or actual GPS signals.
Here's a step-by-step guide on how to simulate location and movement in an Android emulator:
Using the Extended Controls in Android Studio:
a. Start your Android Virtual Device (AVD) in Android Studio.
b. Once the emulator is running, click on the "More" (...) button in the emulator's sidebar.
c. In the Extended Controls window, select the "Location" option from the left sidebar.
Setting a Single Location:
a. In the Location extended control, you'll see a map.
b. You can set a location by:
Clicking anywhere on the map
Entering coordinates in the "Latitude" and "Longitude" fields
Searching for a location using the search bar at the top
c. Once you've selected a location, click "Set Location" to update the emulator's location.
Simulating Movement:
a. In the same Location extended control, you'll see a "Routes" tab next to the "Single points" tab.
b. Click on the "Routes" tab.
c. You can create a route by:
Clicking multiple points on the map to create waypoints
Importing a GPX or KML file with a predefined route
d. Once you've created a route, you can:
Click "Play Route" to start the simulated movement
Adjust the playback speed using the speed multiplier dropdown
Pause, resume, or reset the route playback
Using GPS data in NMEA format:
a. If you have GPS data in NMEA format, you can use the Android Debug Bridge (adb) to send it to the emulator.
b. Open a terminal and use a command like this:
adb emu geo fix <longitude> <latitude>
Replace <longitude> and <latitude> with the desired coordinates.
Using the Geolocation API in Chrome DevTools: If your app uses a WebView or you're testing a web application: a. In the emulator, open Chrome and navigate to your web app. b. In Android Studio, go to Tools > Chrome DevTools. c. In the DevTools window, click on the three-dot menu and select More tools > Sensors. d. In the Sensors tab, you can set geolocation coordinates or choose from preset locations.
Showcasing this in your lab:
Set up the geofence:
In your GeofencingDemo app, set up a geofence for a specific location (e.g., 40.7128, -74.0060 for New York City).
Demonstrate entering the geofence:
Use the emulator's Location control to set the device location outside the geofence.
Then, either set a new location inside the geofence or create a route that enters the geofence.
Show students how the GeofenceBroadcastReceiver logs the "Entered the geofence" message.
Demonstrate exiting the geofence:
Start with the emulator's location inside the geofence.
Create a route or set a new location that moves outside the geofence.
Show how the "Exited the geofence" message is logged.
Simulate real-world scenarios:
Create more complex routes that enter and exit the geofence multiple times.
Adjust the speed of the simulated movement to show how it affects geofence triggering.
Test edge cases:
Simulate movement that just barely enters or exits the geofence.
Test what happens when you set the location exactly on the geofence boundary.
By showcasing these simulation techniques, you can help students understand:
How to effectively test location-based features without physically moving.
The importance of testing various scenarios and edge cases in geofencing applications.
How the Android system handles location updates and geofence transitions.
The relationship between location accuracy, update frequency, and geofence responsiveness.
While emulator testing is crucial, it's also important to test on real devices when possible, as they may behave slightly differently due to real-world factors like GPS accuracy and network conditions.

minus

Lab 1: Introduction: A simple Android Kotlin app that demonstrates the use of FusedLocationProvider to get the current location of the device.

image.png

This will serve as a good starting point for Location Services.
Here's the complete code and instructions for each file:
MainActivity.kt
package com.example.locationdemo

import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.locationdemo.databinding.ActivityMainBinding
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val LOCATION_PERMISSION_REQUEST_CODE = 1

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

binding.btnGetLocation.setOnClickListener {
getLastKnownLocation()
}
}

private fun getLastKnownLocation() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE
)
} else {
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
if (location != null) {
val latitude = location.latitude
val longitude = location.longitude
binding.tvLocation.text = "Latitude: $latitude\nLongitude: $longitude"
} else {
binding.tvLocation.text = "Location not available"
}
}
.addOnFailureListener { e ->
binding.tvLocation.text = "Error getting location: ${e.message}"
}
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
LOCATION_PERMISSION_REQUEST_CODE -> {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
getLastKnownLocation()
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
}
return
}
}
}
}
build.gradle.kts (Project level)
plugins {
id("com.android.application") version "8.1.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
}
build.gradle.kts (Module level)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "com.example.locationdemo"
compileSdk = 33

defaultConfig {
applicationId = "com.example.locationdemo"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}

dependencies {
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("com.google.android.gms:play-services-location:21.0.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="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=".MainActivity">

<Button
android:id="@+id/btnGetLocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Get Location"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/tvLocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Location will appear here"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnGetLocation" />

</androidx.constraintlayout.widget.ConstraintLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LocationDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

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

</manifest>
Instructions to create and run the app:
Open Android Studio and create a new project named "LocationDemo" with the package name "com.example.locationdemo".
Replace the contents of MainActivity.kt, build.gradle.kts (Project level), build.gradle.kts (Module level), activity_main.xml, and AndroidManifest.xml with the provided code.
Sync the project with Gradle files.
Run the app on an emulator or physical device.
This simple app demonstrates the use of FusedLocationProvider by:
Requesting location permissions if not already granted.
Using FusedLocationProviderClient to get the last known location.
Displaying the latitude and longitude when the "Get Location" button is pressed.
The app uses viewBinding for safe and efficient view access, and it follows modern Android development practices. It serves as a good starting point for students to learn about Location Services in Android.

error

OODA (Observe, Orient, Decide, Act) analysis for this Android app code that demonstrates the use of FusedLocationProvider:

Observe:
The app observes the user's interaction with the UI, specifically when the user taps the "Get Location" button.
It observes the device's current location using the FusedLocationProviderClient.
The app observes the status of location permissions (granted or not granted).
Orient:
The app orients itself to the current state of location permissions when the user requests the location.
It orients to the availability of the last known location from the FusedLocationProviderClient.
The app adapts to different scenarios: location available, location not available, or error in getting location.
Decide:
The app decides whether to request location permissions or proceed with getting the location based on the current permission status.
It decides how to handle the location data:
display it if available, show a message if not available, or display an error message if there's a failure.
The app decides how to respond to the user's permission grant or denial.
Act:
The app acts by requesting location permissions if they're not granted.
It acts by calling the FusedLocationProviderClient to get the last known location.
The app acts by updating the UI to display the location information or appropriate messages.
It acts by showing a toast message if the user denies the location permission.
Key OODA Loop elements in the code:
Permission Check (Observe & Orient):

if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED)
Permission Request (Decide & Act):

ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE
)
Getting Location (Act):
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
// Handle location
}
.addOnFailureListener { e ->
// Handle error
}
Handling Location Result (Orient & Act):
if (location != null) {
val latitude = location.latitude
val longitude = location.longitude
binding.tvLocation.text = "Latitude: $latitude\nLongitude: $longitude"
} else {
binding.tvLocation.text = "Location not available"
}
Permission Result Handling (Observe, Orient, Decide, & Act):
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
// Handle permission result
}
This OODA analysis shows how the app continuously observes its environment (user actions and system states), orients itself to the current situation, decides on appropriate actions, and acts accordingly.
This cycle allows the app to respond dynamically to user interactions and system conditions, providing a smooth and responsive user experience while handling location services.


info

Lab 2: Android Kotlin app that demonstrates how to track the device's location as it moves using FusedLocationProvider.

image.png

Learning outcomes: Applying Location Services in Android.


MainActivity.kt
package com.example.locationtrackerdemo

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Looper
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.