I get a particular set of questions quite a bit on my blog and other social media outlets. One of these questions includes how to use geolocation features such as GPS tracking within a NativeScript mobile application for iOS and Android. Many people want to be able to gather location information and in many cases use this information for mapping.
So what does it take to make use of the device GPS hardware for location tracking?
We’re going to see how to create a mobile application for Android and iOS using NativeScript and Angular that makes use of geolocation in a few different ways.
Going into this we need to be aware that there will be limited to zero functionality with geolocation on an emulator. I’ve found location support on the iOS emulator to be decent, but location support on various Android emulators to be terrible. You’ll have a much better time testing these native features on a device instead.
With that said, we can start creating and developing our project.
For simplicity we’re going to create a fresh NativeScript project with Angular, TypeScript, and HTML. This project can be created from the NativeScript CLI by executing the following:
tns create GeoProject --ng
cd GeoProject
tns platform add ios
tns platform add android
The --ng
tag indicates that we are creating an Angular project, not a vanilla NativeScript project. It is important to note that if you’re not using a Mac with Xcode installed, you cannot add and build for the iOS platform.
With the project created, we need to install a plugin that has geolocation features baked in. This plugin can be installed by executing the following:
tns plugin add nativescript-geolocation
If you’d like to see documentation around the plugin, it can be found here. At this point we can start developing our application and its various functionality.
We are going to be building a single page application in an effort to keep things simple. This application will have two labels that display the longitude and latitude as well as three different buttons. One button will forcefully figure out the location of the device and update the labels. The other two buttons will enable or disable watch mode where the labels will automatically update as the device location changes.
Open the project’s app/app.component.ts file and include the following TypeScript code:
import { Component, NgZone } from "@angular/core";
import * as Geolocation from "nativescript-geolocation";
@Component({
selector: "my-app",
templateUrl: "app.component.html",
})
export class AppComponent {
public latitude: number;
public longitude: number;
private watchId: number;
public constructor(private zone: NgZone) {
this.latitude = 0;
this.longitude = 0;
}
private getDeviceLocation(): Promise<any> {
return new Promise((resolve, reject) => {
Geolocation.enableLocationRequest().then(() => {
Geolocation.getCurrentLocation({timeout: 10000}).then(location => {
resolve(location);
}).catch(error => {
reject(error);
});
});
});
}
public updateLocation() {
this.getDeviceLocation().then(result => {
this.latitude = result.latitude;
this.longitude = result.longitude;
}, error => {
console.error(error);
});
}
public startWatchingLocation() {
this.watchId = Geolocation.watchLocation(location => {
if(location) {
this.zone.run(() => {
this.latitude = location.latitude;
this.longitude = location.longitude;
});
}
}, error => {
console.dump(error);
}, { updateDistance: 1, minimumUpdateTime: 1000 });
}
public stopWatchingLocation() {
if(this.watchId) {
Geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
}
There is a lot going on in the above class so we’re going to break it down.
In the constructor
method of the AppComponent
class initializes our variables, but it also injects the Angular NgZone
service. We need this service because we plan to use listeners and update our UI via those listeners.
This is where things get interesting.
The NativeScript documentation says to use the isEnabled
function of the Geolocation
component to determine if the location components are enabled. I found this function to have strange results. Instead we have just the following:
private getDeviceLocation(): Promise<any> {
return new Promise((resolve, reject) => {
Geolocation.enableLocationRequest().then(() => {
Geolocation.getCurrentLocation({timeout: 10000}).then(location => {
resolve(location);
}).catch(error => {
reject(error);
});
});
});
}
In the above we call the enableLocationRequest
method which will resolve if permission is granted or if permission is already granted. After we have permission, we can obtain the current location. This is not instant which is why we are using a promise.
The getDeviceLocation
function is not going to be bound to the UI, hence it is a private function. Instead we are going to use the updateLocation
method for updating the UI with the current location. This is all related to the button that will update the location on demand.
So what if we want to watch for location changes?
This is where the startWatchingLocation
and stopWatchingLocation
methods come into play. Starting with the startWatchingLocation
method we have:
public startWatchingLocation() {
this.watchId = Geolocation.watchLocation(location => {
if(location) {
this.zone.run(() => {
this.latitude = location.latitude;
this.longitude = location.longitude;
});
}
}, error => {
console.dump(error);
}, { updateDistance: 1, minimumUpdateTime: 1000 });
}
A listener is created and the UI is updated in the Angular zone. The listener returns an id which we’ll hold onto because it is necessary when it comes to stopping the listener.
The updateDistance
property indicates we are going to update the location only at a one meter difference, but the minimumUpdateTime
property indicates it will only happen as often as one second. These settings should be adjusted to preserve battery life.
We can now move onto the simple UI. Open the project’s app/app.component.html file and include the following HTML markup:
<ActionBar title="{N} Geolocation Example"></ActionBar>
<GridLayout>
<StackLayout>
<Label text="Latitude: {{ latitude }}"></Label>
<Label text="Longitude: {{ longitude }}"></Label>
<Button class="btn btn-primary btn-active" text="Update" (tap)="updateLocation()"></Button>
<Button class="btn btn-primary btn-active" text="Watch Location" (tap)="startWatchingLocation()"></Button>
<Button class="btn btn-primary btn-active" text="Stop Watching" (tap)="stopWatchingLocation()"></Button>
</StackLayout>
</GridLayout>
In the above UI we have an action bar, but more importantly our buttons which are mapped to the particular functions in our TypeScript file.
We just saw how to use geolocation features such as device GPS in a NativeScript Angular application. A lot more can be done than demonstrated in this article, but it should be enough to get you started. Just remember that location features don’t work well in emulators. Your best bet would be to test them on a device. This isn’t because the plugin for NativeScript isn’t very good, it is because the emulators don’t do a good job with geolocation in general.