Creating a Realtime Handphones Locations Tracker For Android Devices with Google Location API and Google Maps

Ezani
15 min readJul 9, 2021

Today’s handphones contain GPS location tracking which can be used to track the locations of the handphone owners. In this article, I am going to discuss how to program an app (the first app) using Android Studio, together with Google Location API and Google Map, which will then use the GPS functionality found in Android handphones to transmit the current handphone’s geo-coordinates (latitude and longitude) periodically to a receiving app on the server (the second app) which then proceeds to save the latest data to the database.

This transmitting app must be installed on the handphone to be tracked and the handhone must have its Location switch set to ON.

A third and final control center app reads the database and displays the handphone geo-coords. With the help of the Google Location API and Google Map, the geo-coords can be overlayed on a Google map to visually see the location of the handphone user. The app is designed such that multiple handphones can be tracked simply by giving each handphone a unique ID. My control center app was written in ASP but you can use any programming language you are comfortable with once you understand that this app just simply reads the geo-coords data of each handphone and displays it on the Google Map using the Google Maps and Location APIs.

Each handphone’s GPS will transmit the handphone’s current latitude and longitude coordinates to give the current handphone location which is polled in regular time intervals. Each handphone geo-coordinates are then overlayed over a Google Map to give a visual perspective of the location of each handphone holder.The steps taken to develop this application are :

STEP 1 : Get a Google Cloud Platform account, get your Google key/ID (you’ll need this in your code) and select/enable Google Map API (if you don’t have one already)

STEP 2 : Download the latest Android Studio (if you don’t have one already)

STEP 3 : Start coding!

(Note on my earlier articles — For a rundown on fullstack Java and REST backend development and React frontend development, please check out : Creating a fullstack React/Material-UI DataGrid frontend data grid connected to a Java SpringBoot REST GET API backend with axios.

This article is further extended with my other article, Creating a fullstack Java app with AJAX HTML frontend using jsGrid datagrid and Apex Charts chart component calculating monthly averages from uploaded Excel data files on a Spring Boot REST backend running Apache Tomcat.

For a Python-Django REST API stack implementing HTTP GET, check out my other article, Creating a Python Django REST Service backend with Postgres database.

For a node.js-ExpressJS-MongoDB (through mongoose) stack REST API backend with CRUD functionality accessed via a React-Data-Grid and Material-UI/DataGrid, please check out my article, Creating a CRUD node.js REST service backend with node.js, Express and MongoDB (mongoose) and a React-Data-Grid / Material-UI DataGrid frontend to access the service.)

The above article is further extended with Deploying a Node.js server-side backend CRUD REST service to Heroku connected to a cloud-based MongoDB Atlas database which shows how to deploy the node.js app created to Heroku cloud hosting and also to use the cloud-based MongoDB Atlas for public deployment.

Get a Google Cloud Platform account

You may be surprised that, in order to use Google Location API or any Google API, you only just need your Google account. Yup! The same account you use to open your GMail, Google Drive, Google Play or YouTube!

Google Cloud Platform dashboard

With that, just go to Google Console and create a new project by clicking on NEW PROJECT (see below).

Create a new Google Cloud project

Next after you have setup your Google Cloud project, click on the API & Services menu on the left, and click Library to view all the APIs available (see below).

You will see the API Library (screenshot below) which shows all the APIs available from Google. Click on the Maps SDK for Android panel.

Google API Library dashboard on Google Cloud Platform

Since this is the first time you are using Maps SDK for Android, you will need to click the ENABLE button to enable and use it. Click the ENABLE button. (Note that if you already had the Maps SDK enabled, just click the MANAGE button to manage it.)

Google Maps SDK for Android Enable dialog

The most important starting point of the Google Maps Platform is to get your API key to use in your code to enable the API to be used in your app. You can get this key by clicking on Credentials on the menu on the left (see below).

Google Maps Platform menu (Google Maps API)

You will see the screen below.

To create a new API key, click on + CREATE CREDENTIALS link located at the top of the screen. A new API key will be generated for you for Google Maps API. You can use this key in your Google Maps app. Please note you will also have to supply the domain on which your app resides because Google will first need to verify this domain (see below).

Add a domain where you are hosting your Google Map app site

Download Android Studio

After having secured your Google Maps API key, you can head down to the Android Studio website and download the app. Android Studio is an awesome and the de facto app to build native Android applications and it has a beautiful IntelliJ type interface with the default Gradle dependency manager (previously, before Android Studio, Eclipse was used as the de facto Android building IDE).

You can use the many emulator handphone interfaces supplied with Android Studio to test your Android app or you can build and copy the resulting APK (compiled Android package which is ready to run) to your handphone and test it there. Make sure you have set the Android API level used in your code with a compatible API used by your handphone!

Let’s start coding!

The Transmitting App (the first app)

Ok, now that you have downloaded and installed Android Studio and tested it is working properly and building APKs successfully, which can be run on your handphone (or the studio’s android emulators), say, a simple “Hello, World” application, you are now ready to code! (Before you start this project, you might also want to read up on some Android coding basics to familiarize yourself with the Android project structure and how Android works and how the APK is built and copied on the handphone and then executed.)

Below is the project structure for our application consisting of the layout files in XML format and also the Android activity files.

There are two screens in our program so we have two activity and two layout files (see above).

Ok, let’s start with the app’s Gradle file. This file contains information about the target API SDK version the program will be compiled with, and the minimum SDK that the target install handphone must have in order to run your app or APK. This Gradle file also lists down all the dependencies (whether it is an internal Android package or an external package you need to include in your app)

App.gradle:

apply plugin: 'com.android.application'

android {
compileSdkVersion 27
defaultConfig {
applicationId "info.androidhive.androidlocation"
minSdkVersion 21
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:design:27.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation 'com.google.android.gms:play-services-maps:16.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
compile "com.mcxiaoke.volley:library-aar:1.0.0"

// location play services
implementation 'com.google.android.gms:play-services-location:15.0.1'

// dexter runtime permissions
implementation 'com.karumi:dexter:4.2.0'

// ButterKnife view binding
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

Here, you can see that for the Google Maps app, we use two core external packages which are the Volley package, a networking library for Android that manages network requests. It bundles the most important features you’ll need, such as accessing JSON APIs, loading images and String requests, and also the Butterknife package, which is a light weight library to inject views into Android components using annotation processing.

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="info.androidhive.androidlocation">

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> only for Android 10 (API29) and above-->

<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"
>

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--
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"
></activity>

</application>

</manifest>

Next is the Android manifest file. This file contains the permission settings that you set for your app when it runs on your target handphone’s Android operating system. The Android OS is highly secure, with strict permission settings, which you need to manage — such as accessing the camera, telephone or voice recorder, etc.

Important — in this manifest file, we set our Android permission level to ACCESS_FINE_LOCATION, which allows our app to more accurately determine the location of our handphone through GPS. The other setting is ACCESS_COARSE_LOCATION which is not as accurate.

Now, let’s move on to our Android layout files which are saved in XML format. The layout files allow us to design our screen, and stores information on the placement coordinates of our screen’s UI components such as text fields, labels, buttons, image containers and so on, their appearance, color, size and name of font, etc.

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity"
>

<Button
android:id="@+id/btn_start_location_updates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/start_updates"
/>

<Button
android:id="@+id/btn_stop_location_updates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:enabled="false"
android:text="@string/stop_updates"
/>

<Button
android:id="@+id/btn_get_last_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/get_last_location"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:gravity="center_horizontal"
android:text="Location updates will be received only when app is foreground"
/>

<TextView
android:id="@+id/location_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textColor="#333"
android:textSize="18dp"
/>

<TextView
android:id="@+id/updated_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textSize="11dp"
/>

<Button
android:id="@+id/buttonMapView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Switch to Map View"
/>

</LinearLayout>

For our maps layout, which displays our location overlayed on a Google Map, we have a map fragment.

activity_maps.xml:

<?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"
/>

For our main activity file (see below), we place all our required Import statements at the top from our Android, Volley, Butterknife and other libraries used in our app.

We bind our various layout controls (from our layout XML file) to variables in our Java activity file. We have two important constants which are UPDATE_INTERVAL_IN_MILLISECONDS and FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS. These are the update intervals in milliseconds which our GPS will transmit data about the handphone location to our receiving program. The function, updateLocationUI(), will retrieve the latitude and longitude information from the GPS and transmit the geo-coordinates via the Internet (using Volley) to our receiving application.

MainActivity.java:

package info.androidhive.androidlocation;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.common.api.Response;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response.ErrorListener;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.PermissionListener;

import java.text.DateFormat;
import java.util.Date;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;


/**
* Reference: https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates
*/

public class MainActivity extends AppCompatActivity {

private static final String TAG = MainActivity.class.getSimpleName();

@BindView(R.id.location_result)
TextView txtLocationResult;

@BindView(R.id.updated_on)
TextView txtUpdatedOn;

@BindView(R.id.btn_start_location_updates)
Button btnStartUpdates;

@BindView(R.id.btn_stop_location_updates)
Button btnStopUpdates;

@BindView(R.id.buttonMapView)
Button btnMapView;

// location last updated time
private String mLastUpdateTime;

// location updates interval - 10sec
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = 5000;

private static final int REQUEST_CHECK_SETTINGS = 100;


// bunch of location related apis
private FusedLocationProviderClient mFusedLocationClient;
private SettingsClient mSettingsClient;
private LocationRequest mLocationRequest;
private LocationSettingsRequest mLocationSettingsRequest;
private LocationCallback mLocationCallback;
public static Location mCurrentLocation;

// boolean flag to toggle the ui
private Boolean mRequestingLocationUpdates;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);

// initialize the necessary libraries
init();

// restore the values from saved instance state
restoreValuesFromBundle(savedInstanceState);

btnMapView.setOnClickListener(new View.OnClickListener()
{

@Override
public void onClick(View v) {
Intent intent1 = new Intent(getApplicationContext(),MapsActivity.class);
startActivity(intent1);
//startActivityForResult(intent1,2);
}
});



}

private void init() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
mSettingsClient = LocationServices.getSettingsClient(this);

mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
// location is received
mCurrentLocation
= locationResult.getLastLocation();
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());

updateLocationUI();
}
};

mRequestingLocationUpdates = false;

mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
}

/**
* Restoring values from saved instance state
*/
private void restoreValuesFromBundle(Bundle savedInstanceState) {
if (savedInstanceState != null) {
if (savedInstanceState.containsKey("is_requesting_updates")) {
mRequestingLocationUpdates = savedInstanceState.getBoolean("is_requesting_updates");
}

if (savedInstanceState.containsKey("last_known_location")) {
mCurrentLocation = savedInstanceState.getParcelable("last_known_location");
}

if (savedInstanceState.containsKey("last_updated_on")) {
mLastUpdateTime = savedInstanceState.getString("last_updated_on");
}
}

updateLocationUI();
}


/**
* Update the UI displaying the location data
* and toggling the buttons
*/
//private GoogleMap mMap1;

private void updateLocationUI() {
if (mCurrentLocation != null) {
txtLocationResult.setText(
"Lat: " + mCurrentLocation.getLatitude() + ", " +
"Lng: " + mCurrentLocation.getLongitude()
);
//!!!MAKE SURE YOU HAVE VOLLEY FOR INTERNET CONNECTION REQUESTS AND DEXTER RUNTIMES
RequestQueue queue = Volley.newRequestQueue(this);
String currentDateTime2 = DateFormat.getInstance().format(new Date());
String currentDateTime = currentDateTime2.replace(" ", "%20");
String url = "http://zzz.com/mylocator/savemylocator.asp?handphone_id=1&latitude=" + mCurrentLocation.getLatitude() + "&longitude=" + mCurrentLocation.getLongitude() + "&date_time=" + currentDateTime;

StringRequest stringRequest = new StringRequest(Request.Method.GET,url, new com.android.volley.Response.Listener<String>() {
@Override
public void onResponse(String response) {
//Toast.makeText(getApplicationContext(),"Successfully sent.",Toast.LENGTH_SHORT).show();
}
}, new com.android.volley.Response.ErrorListener() {

@Override
public void onErrorResponse (VolleyError error) {
String currentDateTime3 = DateFormat.getInstance().format(new Date());
String currentDateTime1 = currentDateTime3.replace(" ", "%20");
Toast.makeText(getApplicationContext(),error.getMessage() + ". Error with Internet. ",Toast.LENGTH_LONG).show();
}

}
);
queue.add(stringRequest);


// giving a blink animation on TextView
txtLocationResult.setAlpha(0);
txtLocationResult.animate().alpha(1).setDuration(300);

// location last updated time
txtUpdatedOn.setText("Last updated on: " + mLastUpdateTime);
}

toggleButtons();
}

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("is_requesting_updates", mRequestingLocationUpdates);
outState.putParcelable("last_known_location", mCurrentLocation);
outState.putString("last_updated_on", mLastUpdateTime);

}

private void toggleButtons() {
if (mRequestingLocationUpdates) {
btnStartUpdates.setEnabled(false);
btnStopUpdates.setEnabled(true);
} else {
btnStartUpdates.setEnabled(true);
btnStopUpdates.setEnabled(false);
}
}

/**
* Starting location updates
* Check whether location settings are satisfied and then
* location updates will be requested
*/
private void startLocationUpdates() {
mSettingsClient
.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "All location settings are satisfied.");

Toast.makeText(getApplicationContext(), "Started location updates!", Toast.LENGTH_SHORT).show();

//noinspection MissingPermission
mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.myLooper());

updateLocationUI();
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
"location settings ");
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be " +
"fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);

Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
}

updateLocationUI();
}
});
}

@OnClick(R.id.btn_start_location_updates)
public void startLocationButtonClick() {
// Requesting ACCESS_FINE_LOCATION using Dexter library
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(new PermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
mRequestingLocationUpdates = true;
startLocationUpdates();
}

@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
// open device settings when the permission is
// denied permanently
openSettings();
}
}

@Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}

@OnClick(R.id.btn_stop_location_updates)
public void stopLocationButtonClick() {
mRequestingLocationUpdates = false;
stopLocationUpdates();
}

public void stopLocationUpdates() {
// Removing location updates
mFusedLocationClient
.removeLocationUpdates(mLocationCallback)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
Toast.makeText(getApplicationContext(), "Location updates stopped!", Toast.LENGTH_SHORT).show();
toggleButtons();
}
});
}

@OnClick(R.id.btn_get_last_location)
public void showLastKnownLocation() {
if (mCurrentLocation != null) {
Toast.makeText(getApplicationContext(), "Lat: " + mCurrentLocation.getLatitude()
+ ", Lng: " + mCurrentLocation.getLongitude(), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Last known location is not available!", Toast.LENGTH_SHORT).show();
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// Check for the integer request code originally supplied to startResolutionForResult().
case REQUEST_CHECK_SETTINGS:
switch (resultCode) {
case Activity.RESULT_OK:
Log.e(TAG, "User agreed to make required location settings changes.");
// Nothing to do. startLocationupdates() gets called in onResume again.
break;
case Activity.RESULT_CANCELED:
Log.e(TAG, "User chose not to make required location settings changes.");
mRequestingLocationUpdates = false;
break;
}
break;
}
}

private void openSettings() {
Intent intent = new Intent();
intent.setAction(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

@Override
public void onResume() {
super.onResume();

// Resuming location updates depending on button state and
// allowed permissions
if (mRequestingLocationUpdates && checkPermissions()) {
startLocationUpdates();
}

updateLocationUI();
}

private boolean checkPermissions() {
int permissionState = ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION);
return permissionState == PackageManager.PERMISSION_GRANTED;
}


@Override
protected void onPause() {
super.onPause();

if (mRequestingLocationUpdates) {
// pausing location updates
stopLocationUpdates();
}
}
}

Here is the final display of the transmitting app (see screenshot below).

The Receiving App That Stores The Geo-Coords To The Database On The Server (the second app)

Our receiving app is simply another separate application which is able to receive the transmitted geo-coordinates sent and store them to the database on the server. You can store all the coords sent but your database will be excessively huge in a very short time! For this project, we will just overwrite the database table row reserved for our Handphone ID with the latest geo-coordinate location, using an UPDATE … WHERE handphone_id = ? SQL statement or you can also use INSERT REPLACE. You can create your own receiving app based on your programming expertise, but here I will simply just create an Active Server Pages (ASP) server-side file to do this which simply receives the geo-coords and handphone ID from the URL link in a GET request and subsequently stores to the MySQL database on the server with the SQL statement mentioned earlier.

The basic structure of the database table will be simply:

trx_datetime TIMESTAMP (DATE_TIME),
handphone_id INTEGER,
latitude FLOAT or DOUBLE,
longitude FLOAT or DOUBLE
remarks VARCHAR2

Here is the receiving app ASP code below:

SaveLocation.asp:

<HTML>

<HEAD>

<TITLE>Save Location</TITLE>

</HEAD>

<BODY>

<%
strConnection = “Driver={“MySQLDriver;”
Set DbObj = Server.CreateObject(“ADODB.Connection”)
Set oRs = Server.CreateObject(“ADODB.Recordset”)
DbObj.Open strConnection

strSQL = “UPDATE mylocator SET date_time = ‘“ & Request.QueryString(“date_time”) & “‘, latitude = “ & Request.QueryString(“latitude”) & “, longitude = “ & Request.QueryString(“longitude”) & “ WHERE handphone_id = “ & Request.QueryString(“handphone_id”)

Set oRs = DbObj.Execute(strSQL)
‘Response.Write(strSQL)

DbObj.Close
Set DbObj = Nothing
%>

</BODY>

</HTML>

Creating the map display control center app (the third app)

As with the receiving app earlier, you can also write this command center app which simply displays all the handphone latest geo-coords which it retrieves from the database and displays them on a Google map using the Google Location API (you will need your API key to show the Google map on your app). For this article, I simply use classic ASP.

Here is the command center handphones locations display app’s ASP code below:

MyLocator.asp:

<html>
<head>

<title>My Locator (by Ezani)</title>
<meta http-equiv=”refresh” content=”15">
<meta name=”viewport” content=”initial-scale=1.0, user-scalable=no”>
<meta charset=”utf-8">

<style>
/* Always set the Google map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>

<script src=”https://maps.googleapis.com/maps/api/js?key=put_your_Google_Map_API_key_here"></script>
<script>

var map;

function initialize(a,b) {

var mapOptions = {
zoom: 8,
center: {lat: a, lng: b}
};
map = new google.maps.Map(document.getElementById(‘map’),
mapOptions);

}

function loadAllMarkers() {
for (i = 1; i < 3; i++) {addMarker(Number(document.getElementById(i + ‘_lat’).value), Number(document.getElementById(i + ‘_long’).value) , document.getElementById(i + ‘_ident’).value);}
}

function addMarker(c,d,e) {

var marker = new google.maps.Marker({
position: {lat: c, lng: d},
map: map,
title: e
});

var infowindow = new google.maps.InfoWindow({
content: ‘<p>Marker Location:’ + marker.getPosition() + ‘</p>’
});

google.maps.event.addListener(marker, ‘click’, function() {
infowindow.open(map, marker);
});
}

google.maps.event.addDomListener(window, ‘load’, initialize);
</script>
</head>

<body onload=”initialize(3.11,101.5);loadAllMarkers();”>
<div id=”map”></div>

<%
On Error Resume Next

Dim latt(2)
Dim longg(2)
Dim ident(2)

strConnection = “Driver={“MySQL;”
Set DbObj = Server.CreateObject(“ADODB.Connection”)
DbObj.Open strConnection

strSQL = “ SELECT date_time, identifier, latitude, longitude FROM mylocator WHERE handphone_id = 1”
Set oRs = Server.CreateObject(“ADODB.recordset”)
Set oRs = DbObj.Execute(strSQL)

recsize = 0
Do While Not oRs.EOF

recsize = recsize + 1
latt(recsize) = oRs.Fields(“latitude”).Value
longg(recsize) = oRs.Fields(“longitude”).Value
ident(recsize) = Trim(oRs.Fields(“identifier”).Value) + “ “ + Trim(oRs.Fields(“date_time”).Value)
%>

<input type=”text” id = “<%=recsize%>_lat” value =”<%=oRs.Fields(“latitude”).Value%>”>
<input type=”text” id = “<%=recsize%>_long” value =”<%=oRs.Fields(“longitude”).Value%>”>
<input type=”text” id = “<%=recsize%>_ident” value =”<%=Trim(oRs.Fields(“identifier”).Value) + “ “ + Trim(oRs.Fields(“date_time”).Value)%>”>

<%
oRs.MoveNext
Loop
%>

</body>
</html>

And this is how it looks like (see screenshot below):

Google Handphone Location Tracker app

There you have it! An Android-based multiple handphones tracker which you can visually view in map form on their whereabouts, which can be put to good use!

--

--

Ezani

38+ years as a programmer. Started with VB, now doing Java, SpringBoot, Android, React, ASP and Python. Running my own software company at mysoftware2u.com