Location Permissions and Accuracy: Ionic 4

Building an App which captures the Device Location??!! Alright..
Lets start with some of the approaches I tried and failed while developing an Hybrid App using Ionic Framework.

I am using Ionic 4. The App mainly captures an User’s Location at certain events. Accuracy of the location is really important for the App to work correctly.

I tried multiple, literally all the native plugins available. Some failed, some were ‘OK’ and finally I tried a Mixture of Plugins to achieve what I was intending to do. Lets see each Approach.

Approach 1: Geolocation Plugin
Ionic Native Geolocation plugin claims to provide the Latitude and Longitude of the Current Device Position. It actually does.
The getCurrentPosition function sometimes act weirdly. Like delay in response, incorrect Lat-Lng, etc.
The main issue I faced was, the plugin most of the times returns a cached location.
I have researched a lot related to this issue. Many suggested to use maximumAge option with a value as low as possible. Didn’t help. Some suggested to use enableHighAccuracy, but somehow all these suggestions didn’t worked for me.

Still lets have a look how to implement and use this Plugin.

Installation

$ ionic cordova plugin add cordova-plugin-geolocation
$ npm install @ionic-native/geolocation

Add following in your app.module page:

import { Geolocation } from '@ionic-native/geolocation/ngx';

And export the same under providers:

providers: [
Geolocation,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
],

Create a service, say location.service. And add the following code:

import { Injectable } from '@angular/core';
import { Geolocation, GeolocationOptions, Geoposition, PositionError } from '@ionic-native/geolocation/ngx';
@Injectable()export class LocationsProvider {
options: GeolocationOptions;
currentPos: Geoposition;
constructor(
private geolocation: Geolocation,
) {}
getUserPosition() {
return new Promise((resolve, reject) => {
this.options = {
maximumAge: 3000,
enableHighAccuracy: true
};

this.geolocation.getCurrentPosition(this.options).then((pos: Geoposition) => {
this.currentPos = pos;
const location = {
lat: pos.coords.latitude,
lng: pos.coords.longitude,
time: new Date(),
};
console.log('loc', location);
resolve(pos);
}, (err: PositionError) => {
console.log("error : " + err.message);
reject(err.message);
});
});
}
}

The getCurrentPosition function will return the current position in latitude and longitude.

Approach 2: Background Geolocation

This is really an awesome plugin, has a lot of benefits. You can directly send the device location directly to your Database by just providing with an API. But this will happen only when the device is online. Obviously the API call will require an active internet to make a connection to your server. This plugin works even the App is in background, when subscribed.

This will actually help you when you want to track a User’s movement.

Use of Background Geolocation to show the User’s Movement on a Google Map

Installation

ionic cordova plugin add @mauron85/cordova-plugin-background-geolocationnpm install @ionic-native/background-geolocation

Add following in your app.module page:

import { BackgroundGeolocation } from '@ionic-native/background-geolocation/ngx';

And export the same under providers:

providers: [
BackgroundGeolocation,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
],

Create a service, say activity-tracker.service. And add the following code:

import { Injectable, NgZone } from '@angular/core';import { Geolocation, Geoposition, GeolocationOptions } from '@ionic-native/geolocation/ngx';import { BackgroundGeolocation, BackgroundGeolocationResponse, BackgroundGeolocationConfig } from '@ionic-native/background-geolocation/ngx';@Injectable({
providedIn: 'root'
})
export class LocationTrackerProvider {
public watch: any;
public lat: number = 0;
public lng: number = 0;
options: GeolocationOptions;
currentPos: Geoposition;
time: any;
constructor(
public zone: NgZone,
public backgroundGeolocation: BackgroundGeolocation,
private geolocation: Geolocation,
) {}
async startTracking() {
let config: BackgroundGeolocationConfig = {
desiredAccuracy: 10,
stationaryRadius: 0,
distanceFilter: 0,
notificationTitle: 'Background Tracking',
notificationText: 'Enabled',
debug: false,
interval: 5*60*1000,
stopOnTerminate: false,
url: 'http://medtrix.medimojo.co/api/v3/store-geo-location',
httpHeaders: {
'Content-Type': 'application/json'
},
postTemplate: {
lat: '@latitude',
lng: '@longitude',
time: '@time',
},
}
this.backgroundGeolocation.configure(config).then((location: BackgroundGeolocationResponse) => {
this.zone.run(() => {
this.lat = location.latitude;
this.lng = location.longitude;
this.time = location.time;
});
}, (err) => {
console.log(err);
});
// Turn ON the background-geolocation system.this.backgroundGeolocation.start();// Foreground Trackinglet options = {
frequency: 3000,
enableHighAccuracy: true
};
this.watch = this.geolocation.watchPosition(options)
.pipe(filter((p: any) => p.code === undefined))
.subscribe((position: Geoposition) => {
// Run update inside of Angular's zone
this.zone.run(() => {
this.lat = position.coords.latitude;
this.lng = position.coords.longitude;
this.time = new Date();
const location = {
src: 'bg trcker',
lat: position.coords.latitude,
lng: position.coords.longitude,
time: new Date(),
};
});
});
}
stopTracking() {
// console.log('stopTracking');
this.backgroundGeolocation.finish();
this.watch.unsubscribe();
}
}

Final Approach:
The main reason behind these location related issue is, the modern custom Android OS tampers with the GPS service of the Device in order to save battery consumption and provide a higher battery backup.

Location sources includes Global Positioning System (GPS) and location inferred from network signals such as IP address, RFID, WiFi and Bluetooth MAC addresses, and GSM/CDMA cell IDs.
When the device enters power saving mode, it starts returning the previously cached location stored in the device. Or it might be when there is no Mobile Service, no WiFi.
Also sometimes user Denies location accessing permissions to the App.

To deal with all these issues, I finally implemented a solution, which I will describe now.

Plugins required

Geolocation:
ionic cordova plugin add cordova-plugin-geolocation
npm install @ionic-native/geolocation
Location Accuracy
ionic cordova plugin add cordova-plugin-request-location-accuracy
npm install @ionic-native/location-accuracy
Android Permissions
ionic cordova plugin add cordova-plugin-android-permissions
npm install @ionic-native/android-permissions

Add following in your app.module page:

Geolocation:
import { BackgroundGeolocation } from '@ionic-native/background-geolocation/ngx';
Location Accuracy
import { LocationAccuracy } from '@ionic-native/location-accuracy/ngx';
Android Permissions
import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';

And export the same under providers:

providers: [
Geolocation,
AndroidPermissions,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
],

Create a service, say plugin.service. And add the following code:

import { Injectable } from '@angular/core';
import { Diagnostic } from '@ionic-native/diagnostic/ngx';
import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';
import { Geolocation, GeolocationOptions, Geoposition, PositionError } from '@ionic-native/geolocation/ngx';
import { LocationAccuracy } from '@ionic-native/location-accuracy/ngx';
import { reject } from 'q';
@Injectable()export class PluginProvider {
options: GeolocationOptions;
currentPos: Geoposition;
subscription: any;
locationCoords: any;
apiResponse: any;
constructor(
private diagnostic: Diagnostic,
private androidPermissions: AndroidPermissions,
private geolocation: Geolocation,
private locationAccuracy: LocationAccuracy
) {
this.locationCoords = {
latitude: "",
longitude: "",
accuracy: "",
timestamp: ""
}
}
//To check whether Location Service is enabled or Not
async locationStatus() {
return new Promise((resolve, reject) => {
this.diagnostic.isLocationEnabled().then((isEnabled) => {
console.log(isEnabled);
if (isEnabled === false) {
resolve(false);
} else if (isEnabled === true) {
resolve(true);
}
})
.catch((e) => {
// this.showToast('Please turn on Location');
reject(false);
});
});
}
async checkLocationEnabled() {
return new Promise((resolve, reject) => {
this.diagnostic.isLocationEnabled().then((isEnabled) => {
console.log(isEnabled);
if (isEnabled === false) {
this.showToast('Please turn on Location Service');
resolve(false);
} else if (isEnabled === true) {
this.checkGPSPermission().then((response) => {
console.log(response, 'checkGPSPermission-checkLocationEnabled');
this.apiResponse = response;
if(this.apiResponse === false) {
reject(false);
} else {
resolve(this.apiResponse);
}
})
.catch((e) => {
console.log(e, 'checkGPSPermission-checkLocationEnabled');
reject(false);
});
}
})
.catch((e) => {
this.showToast('Please turn on Location');
reject(false);
});
});
}
//Check if application having GPS access permissionasync checkGPSPermission() {
return new Promise((resolve, reject) => {
this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.ACCESS_COARSE_LOCATION).then(
result => {
console.log(result.hasPermission);
if (result.hasPermission) {
console.log('hasPermission-YES');
//If having permission show 'Turn On GPS' dialogue
this.askToTurnOnGPS().then((response) => {
console.log(response, 'askToTurnOnGPS-checkGPSPermission');
if (this.apiResponse === false) {
reject(this.apiResponse);
} else {
resolve(this.apiResponse);
}
});
} else {
console.log('hasPermission-NO');
//If not having permission ask for permission
this.requestGPSPermission().then((response) => {
console.log(response, 'requestGPSPermission-checkGPSPermission');
this.apiResponse = response;
if (this.apiResponse === false) {
reject(this.apiResponse);
} else {
resolve(this.apiResponse);
}
});
}
},
err => {
alert(err);
reject(false);
});
});
}
async requestGPSPermission() {
return new Promise((resolve, reject) => {
this.locationAccuracy.canRequest().then((canRequest: boolean) => {
if (canRequest) {
console.log("4");
} else {
//Show 'GPS Permission Request' dialogue
this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.ACCESS_COARSE_LOCATION).then(() => {
// call method to turn on GPS
this.askToTurnOnGPS().then((response) => {
console.log(response, 'askToTurnOnGPS-requestGPSPermission');
this.apiResponse = response;
if (this.apiResponse === false) {
reject(this.apiResponse);
} else {
resolve(this.apiResponse);
}
});
},
error => {
//Show alert if user click on 'No Thanks'
alert('requestPermission Error requesting location permissions ' + error);
reject(false);
});
}
});
});
}
async askToTurnOnGPS() {
return new Promise((resolve, reject) => {
this.locationAccuracy.request(this.locationAccuracy.REQUEST_PRIORITY_HIGH_ACCURACY).then((resp) => {
console.log(resp, 'location accuracy');
// When GPS Turned ON call method to get Accurate location coordinates
if(resp['code'] === 0) {
resolve(this.apiResponse);
this.getLocationCoordinates().then((cords) => {
console.log(cords, 'coords');
this.apiResponse = cords;
if(this.apiResponse === false) {
reject(false);
} else {
resolve(this.apiResponse);
}
});
}
error => {
alert('Error requesting location permissions');
reject(false);
}
});
});
}
async getLocationCoordinates() {
return new Promise((resolve, reject) => {
this.geolocation.getCurrentPosition().then((resp) => {
this.locationCoords.latitude = resp.coords.latitude;
this.locationCoords.longitude = resp.coords.longitude;
this.locationCoords.accuracy = resp.coords.accuracy;
this.locationCoords.timestamp = resp.timestamp;
console.log(resp, 'get locc');
resolve(this.locationCoords);
}).catch((error) => {
alert('Error getting location');
reject(false);
});
});
}
}

The above service handles all your cases related to starting from Location Permissions to fetching an accurate location.

This fixed my location related issue in my App.
Hope this will guide you, if you are building an App which requires capturing of Location.

P.S.: This observation completely based on my experience. There might be some mistakes in my understanding as well.

In case you need any help related to Location in Ionic or you have some suggestions, feel free to drop an email at rajesh.mishra2295@gmail.com

A coder by profession, sharing life experiences with the Words. Learning more about Productivity, Habits, Decision Making and ambitious towards self freedom.