Implement geofencing in Ionic
You will need Node and npm installed on your machine. A basic understanding of Angular, TypeScript and Ionic will be helpful.
Introduction
A geo-fence is a virtual perimeter for a real-world geographic area. Geofencing is the use of GPS or RFID technology to create a virtual geographic boundary, enabling software to trigger a response when a mobile device enters or leaves a particular area.
Using Ionic, you can create a mobile application using web technologies and use a wide array of existing components. Using Pusher, we can enable realtime functionalities in the application using Pusher’s pub/sub pattern.
We’ll be building a realtime location tracker application using Pusher, Ionic and the Google Maps library. Using the application, admin users will be notified when a user exits a predefined geofence. When this happens, the admin user receives a notification containing the user’s current location
Here’s a demo of the final product:
Prerequisites
To follow this tutorial a basic understanding of Angular, Ionic and Node.js is required. Please ensure that you have Node and npm installed before you begin.
We’ll be using these tools to build out our application:
We’ll be sending messages to the server and using Pusher’s pub/sub pattern, we’ll listen to and receive messages in realtime. To make use of Pusher you’ll have to create an account here.
After account creation, visit the dashboard. Click Create new Channels app, fill out the details, click Create my app, and make a note of the details on the App Keys tab.
Let’s build!
Setup and folder structure
We’ll initialize our project using the Ionic CLI (command line interface). First, install the CLI by running npm install -g ionic
in your terminal. NPM is a package manager used for installing packages. It will be available on your PC if you have Node installed.
To create a new Ionic project called geofence``-app
using the CLI, open a terminal and run:
ionic start geofence-app tabs
The command uses the CLI to create a new project using the tabs template. Follow the prompt and integrate your app with Cordova to target IOS and Android.
Type Y to integrate Cordova into the application. The next prompt will ask if you want to integrate Ionic pro into the application. If you have an Ionic pro account, type Y and N if you don’t.
The Ionic team provides three ready made starter templates. You can check out the rest of the templates here.
If you don’t have Cordova installed on your PC, install it by running the following command:
npm install -g cordova
Open the newly created folder, your folder structure should look something like this:
geofence-app/
resources/
node_modules/
src/
app/
app.component.html
app.module.ts
app.scss
...
assets/
...
pages/
home/
about/
...
Open a terminal inside the project folder and start the application by running ionic serve
. A browser window should pop up and you should see screenshot below.
Installing dependencies
Next, run the following commands in the root folder of the project to install dependencies.
// install depencies required to build the server
npm install express body-parser dotenv pusher sentiment uuid
// front-end dependencies
npm install pusher-js @types/pusher-js @agm/core
Building our server
Now that we have our application running, let’s build out our server.
To do this we’ll make user of Express. Express is a fast, unopinionated, minimalist web framework for Node.js. We’ll use this to receive requests from our Angular application.
Create a file called server.js
in the root of the project and update it with the code snippet below:
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = process.env.PORT || 4000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
We referenced three packages in the snippet above, body-parser
, pusher
and dotenv
. Let’s get into what each one does.
- body-parser is a package used to parse incoming request bodies in a middleware before your handlers, available under the
req.body
property. - dotenv is a zero-dependency module that loads environment variables from a
.env
file into[process.env](https://nodejs.org/docs/latest/api/process.html#process_process_env)
. This package is used so sensitive information like theappId
andsecret
aren’t added to our codebase directly. - The dotenv package will load the variables provided in our
.env
file into our environment.
The dotenv
package should always be initialized very early in the application at the top of the file. This is because we need the environment variables available throughout the application.
The calls to our endpoint will be coming in from a different origin, therefore we need to make sure we include the CORS headers (Access-Control-Allow-Origin
). If you are unfamiliar with the concept of CORS headers, you can find more information here.
Also, you’ll notice that we installed Pusher library as a dependency. Visit the Pusher website to create a Pusher account if you haven’t done so already.
Create a .env
file to load the variables we’ll be needing into the Node environment. The file should be in the root folder of your project. Open the file and update it with the code below.
// .env
PUSHER_APP_ID=<APP_ID>
PUSHER_KEY=<PUSHER_KEY>
PUSHER_SECRET=<PUSHER_SECRET>
PUSHER_CLUSTER=<PUSHER_CLUSTER>
P.S: Please ensure you replace the placeholder values above with your Pusher
appId
,key
,secret
andcluster
.
This is a standard Node application configuration, nothing specific to our app.
Realtime location updates
To enable users send in their current receive messages, we’ll create a route to handle incoming requests. Update your server.js
file with the code below.
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const Pusher = require('pusher');
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: process.env.PUSHER_CLUSTER,
encrypted: true,
});
...
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
app.post('/ping', (req, res) => {
const { lat, lng } = req.body;
const data = {
lat,
lng,
};
pusher.trigger('location', 'ping', data);
res.json(data);
});
...
- We created a
POST /ping
route which, when hit, triggers a Pusher event. - We used object destructuring to get the body of the request, we also got the
lat
andlng
in the request body sent by the user. - The
data
object will contain thelat
andlat
sent by the user. - The trigger is achieved using the
trigger
method which takes the trigger identifier(location
), an event name (ping
) and a payload(data
). - The payload can be any value, but in this case, we have a JS object.
- The response will contain the
data
object.
Geofence updates
We’ll need another route to notify admin users when a user leaves their defined geofence. Let’s create a POST /notify
route to handle incoming updates on the user’s geofence status. Update the server.js
file to include the new route.
//server.js
...
app.post('/ping', (req, res) => {
...
});
app.post('/notify', (req, res) => {
const { lat, lng } = req.body;
const data = {
lat,
lng,
};
pusher.trigger('geofence', 'exit', data);
res.json(data);
});
...
You can now start the server by running node server.js
in a terminal in the root folder of the project.
Home page
Let’s build out the home page for our application. We’ll be creating two pages for this project, the home page and the admin page. Typically, the admin page would have been a separate application but in this context, we’ll limit it to just a page on this project.
Since we’re using a starter template, we already have some pages created in the pages
directory.
We’ll edit and refactor the home
page to suit our needs. The home
page will display the user’s current location on the map using a marker and a string of containing the user’s formatted address. Open the home.html
file and replace the contents with the snippet below:
<!-- home.html -->
<ion-header>
<ion-navbar>
<ion-title>Home</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<map [center]="center" [zoom]="zoom">
</map>
<div class="location-address">
<p>{{address}}</p>
<h4>{{state}}</h4>
</div>
</ion-content>
We’ve referenced a map
component that hasn’t been created, we’ll get to creating it but before then let’s add some styles to the home.scss
file. Open the file and copy the following content into it:
// home.scss
page-home {
ion-content {
position: relative;
agm-map {
height: 100%;
width: 100%;
}
.location-address {
width: 100%;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
background: white;
position: absolute;
bottom: 0;
left: 0;
padding: 0 23px 10px;
p{
font-size: 20px;
opacity: .7;
font-weight: bold;
letter-spacing: .2px;
margin-bottom: 4px;
}
h4{
font-size: 14px;
opacity: 0.4;
font-weight: 500;
margin: 0;
}
}
}
}
Update the home.ts
file below to be similar to the snippet below:
import { Component, OnInit } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { MapsAPILoader } from '@agm/core';
import { HttpClient } from '@angular/common/http';
declare const google;
@Component({
selector: 'page-home',
templateUrl: 'home.html',
})
export class HomePage implements OnInit {
constructor(
public navCtrl: NavController,
private loader: MapsAPILoader,
private http: HttpClient,
) {
}
center = {
lat: 6.4393477,
lng: 3.5244628999999996,
};
zoom = 15;
address = '';
state = '';
pingLocation(location) {
this.http
.post('http://localhost:4000/ping', location)
.subscribe((res) => {});
}
notify(location) {
this.http
.post('http://localhost:4000/notify', location)
.subscribe((res) => {});
}
ngOnInit() {
this.loader.load().then(() => {
this.pingLocation(this.center);
});
}
}
Realtime location updates
We’ll be updating the user’s location in realtime and also sending live location data of the user to the server. For this we’ll be using the native Geolocation API by ionic. The API will watch the user’s current position and update the marker on the map.
First, we’ll install the Geolocation plugin. Run the following commands to add the plugin to your project.
ionic cordova plugin add cordova-plugin-geolocation --variable GEOLOCATION_USAGE_DESCRIPTION="To locate you"
npm install --save @ionic-native/geolocation
Update the home.ts
file with the snippet below:
// home.ts
...
import { MapsAPILoader } from '@agm/core';
import { Geolocation } from '@ionic-native/geolocation';
...
export class HomePage implements OnInit{
constructor(
...
private platform: Platform,
private geolocation: Geolocation
){};
...
notify(location) {
...
}
reverseGeocode(latLng) {
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: latLng }, (results, status) => {
if (status === 'OK') {
if (results[0]) {
const address = results[0].formatted_address;
const addressList = address.split(',');
this.address = addressList[0];
this.state = addressList.slice(2).join(', ');
}
}
});
}
ngOnInit(){
this.loader.load().then(() => {
this.reverseGeocode(this.center);
this.pingLocation(this.center);
});
this.platform.ready().then(() => {
if (this.platform.is('cordova')) {
const watch = this.geolocation.watchPosition();
watch.subscribe((position) => {
const positionEmpty = Object.keys(position).length < 1;
if (!positionEmpty) {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
this.reverseGeocode(this.center);
this.pingLocation(this.center);
}
});
}
});
}
}
In the snippet above, we added a new method to the HomePage
component. The reverseGeocode
method uses the Google Maps Geocoder
API to reverse geocode coordinates to addresses. In the function we split the formatted_address
property getting the short_name
, state
and country
.
The ngOnInit
lifecycle got an update. First we reverse geocoded the current address after the Google Maps script has been loaded. We then check if the platform is Cordova supported. If it is, we initialize the geolocation API to watch the user’s location, subscribing to the watch
variable initiates the process. When the user’s location is returned, we reverse geocode the coordinates to get the address, then we send the coordinates to the server.
After this update, you should get an error that the Geolocation
and HttpClient
providers haven’t been registered. To clear this error, we’ll register the these providers in the app.module.ts
file. Open the file and add them to the list of providers.
// app.module.ts
...
import { SplashScreen } from '@ionic-native/splash-screen';
import { Geolocation } from '@ionic-native/geolocation';
import { HttpClientModule, HttpClient } from '@angular/common/http';
@NgModule({
...
imports: [
...
HttpClientModule
],
...
providers: [
...
Geolocation,
HttpClient,
],
})
export class AppModule {}
Setting up geofences
To set up geofences for the user, we’ll make use of the native Geofence Ionic API. Install this plugin by running the following commands in a terminal in the root folder of your project.
ionic cordova plugin add cordova-plugin-geofence
npm install --save @ionic-native/geofence
The next step is to use the Geofence provider in the HomePage
component. Open the home.ts
file and update it like so:
// home.ts
...
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { Geofence } from '@ionic-native/geofence';
import { v4 } from 'uuid';
...
export class HomePage implements OnInit {
constructor(
...
private geofence: Geofence
){
if (this.platform.is('cordova')) {
this.platform.ready().then((_) => {
geofence.initialize().then((_) => {
console.log('Geofence Plugin Ready');
});
});
}
}
...
private createGeofence() {
let fence = {
id: v4(), //any unique ID
latitude: this.center.lat, //center of geofence radius
longitude: this.center.lng,
radius: 1000, //radius to edge of geofence in meters
transitionType: 2,
};
this.geofence
.addOrUpdate(fence)
.then(
() => console.log('Geofence added'),
(err) => console.log('Geofence failed to add', err)
);
this.geofence.onTransitionReceived().subscribe((res) => {
this.notify(this.center);
});
}
ngOnInit(){
this.loader.load().then(() => {
...
});
this.platform.ready().then(() => {
if (this.platform.is('cordova')) {
this.createGeofence();
const watch = this.geolocation.watchPosition();
...
}
});
}
}
First we check if the platform is supported by Cordova and if the platform is ready. Then we initialize the Geofence
provider in the constructor
. In the createGeofence
method, we define a fence
object. The id
, latitude
, longitude
and radius
properties are kind of self explanatory. The transitionType
is one of three possible types. A geofence has three transition types:
- Enter
- Leave
- Both
Each type is represented by accordingly by the numbers 1, 2 and 3. Next, we call the addOrUpdate
method of the geofence provider, the method takes in the fence
object we created.
We’ll be listening for the transition type of leave
, which means we should get notified when the user leaves the set geofence.
Updating the ngOnInit
lifecycle to call the createGeofence
method ensures that the geofence is created once the component is initialized. Include the Geofence
provider in the module file to prevent the app from throwing errors. Add the Geofence
to the list of providers in the app.module.ts
file.
// app.module.ts
...
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { Geofence } from '@ionic-native/geofence';
@NgModule({
...
providers: [
StatusBar,
SplashScreen,
{ provide: ErrorHandler, useClass: IonicErrorHandler },
Geolocation,
HttpClient,
Geofence,
],
})
export class AppModule {}
Setting up the map component
We referenced an uncreated map
component in the home.html
file. Create a components
folder in the src
directory. Within it, create a map
folder with three files in it. map.ts
, map.html
and map.scss
.
For the map component, we’ll be using components provided by the angular-google-maps library. Let’s register the angular-google-maps module and the new map component in the app.module.ts
file. Open the app.module.ts
file and update the imports
and declarations
properties.
// app.module.ts
...
import { Geofence } from '@ionic-native/geofence';
import { AgmCoreModule } from '@agm/core';
import { MapComponent } from '../components/map/map';
@NgModule({
declarations: [
...
MapComponent
],
imports: [
...
AgmCoreModule.forRoot({
// please add your own API key here: https://developers.google.com/maps/documentation/javascript/get-api-key?hl=en
apiKey: 'GOOGLE_API_KEY',
libraries: ['geometry'],
}),
],
...
})
export class AppModule {}
Now, open the map.html
file and copy the content below into it:
<!-- map.html -->
<agm-map [latitude]="center.lat" [longitude]="center.lng" [zoom]="zoom">
<agm-marker [latitude]="center.lat" [longitude]="center.lng" [iconUrl]="'assets/imgs/user.svg'"></agm-marker>
<agm-circle [latitude]="radiusCenter.lat" [longitude]="radiusCenter.lng" [radius]="500" [fillColor]="'#ADC1B9'"></agm-circle>
</agm-map>
P.S: All assets used in the project are available on GitHub
Next, let’s update the map.scss
file to feature styles for the component:
// map.scss
map {
agm-map{
width: 100%;
height: 100%;
}
}
That’s it. Pretty straightforward. Let’s define the missing variables for the map component in the map.ts
file.
// map.ts
import { Component, Input } from '@angular/core';
/**
* Generated class for the MapComponent component.
*
* See https://angular.io/api/core/Component for more info on Angular
* Components.
*/
@Component({
selector: 'map',
templateUrl: 'map.html',
})
export class MapComponent {
text: string;
constructor() {}
@Input()
center = {
lat: 6.435838,
lng: 3.451384,
};
@Input() zoom = 15;
radiusCenter = {
lat: 6.435838,
lng: 3.451384,
}
}
The map component will feature two inputs, center
and zoom
. The center
input will set the center of the map and the zoom
will do the same. Pretty self-explanatory. The radiusCenter
will be used to define the circle drawn on the map, displaying the geofence set up. Below is a screenshot of the home page after all the changes made.
Introducing Pusher and updating location in realtime
So far we have an application that allows users to send location data but the location isn’t updated in realtime. To solve this problem, we’ll include the Pusher library.
Let’s create a Pusher service to be used application wide. The Angular CLI can aid in the service creation. Open a terminal in your project’s root folder and run the following command.
ionic generate provider pusher
This command simply tells the CLI to generate a provider named pusher
. Now open the pusher.ts
file in the src/providers/pusher
directory and update it with the code snippet below:
// src/providers/pusher/pusher.ts
import { Injectable } from '@angular/core';
import Pusher from 'pusher-js';
@Injectable()
export class PusherProvider {
constructor() {
this.pusher = new Pusher('PUSHER_KEY', {
cluster: 'PUSHER_CLUSTER',
encrypted: true,
});
}
pusher;
public init(channelName) {
const channel = this.pusher.subscribe(channelName);
return channel;
}
}
- First, we initialize Pusher in the constructor.
- The
init
method returns the Pusher property we created.
Note: Ensure you replace the
PUSHER_KEY
andPUSHER_CLUSTER
string with your actual Pusher credentials.
Admin page
The admin page will feature two segments, location
and notifications
. The location segment will display the user’s current location at any particular time. Create the admin page using the CLI by running the following command:
ionic generate page admin
You’ll find the admin
page generated in the pages
folder. Open the admin.html
file and copy the code below into it:
<!-- admin.html -->
<ion-header>
<div padding>
<ion-segment [(ngModel)]="tab">
<ion-segment-button value="location">
Position
</ion-segment-button>
<ion-segment-button value="notifications">
Notifications
</ion-segment-button>
</ion-segment>
</div>
</ion-header>
<ion-content>
<div [ngSwitch]="tab" style="height: 100%">
<div *ngSwitchCase="'location'" style="height: 100%">
<map [center]="center"></map>
</div>
<div *ngSwitchCase="'notifications'" class="notifications-holder">
<h4 class="no-notifications" *ngIf="!notifications.length">No notifications to display</h4>
<ion-list *ngIf="notifications.length">
<ion-item *ngFor="let notification of notifications">
<ion-thumbnail item-start>
<ion-icon name="person"></ion-icon>
</ion-thumbnail>
<h2>{{notification.header}}</h2>
<p>{{notification.body}}</p>
</ion-item>
</ion-list>
</div>
</div>
</ion-content>
Style up the page by copying the following styles into the admin.scss
file.
agm-map {
width: 100%;
height: 100%;
}
ion-thumbnail {
display: flex;
align-items: center;
justify-content: center;
background: #adc1b9;
border-radius: 50%;
ion-icon {
font-size: 2.2em;
color: whitesmoke;
}
}
.no-notifications {
font-size: 14px;
color: deepskyblue;
text-transform: uppercase;
text-align: center;
}
Let’s update the admin.ts
file to include the variables we used in the HTML file:
// admin.ts
import { Component, OnInit } from '@angular/core';
import { NavController } from 'ionic-angular';
import { PusherProvider } from '../../providers/pusher/pusher';
import { Platform } from 'ionic-angular';
import { PhonegapLocalNotification } from '@ionic-native/phonegap-local-notification';
declare const google;
@Component({
selector: 'page-admin',
templateUrl: 'admin.html',
})
export class AdminPage implements OnInit {
constructor(
public navCtrl: NavController,
private pusher: PusherProvider,
private platform: Platform,
private localNotification: PhonegapLocalNotification
) {
if (platform.is('cordova')) {
platform.ready().then((_) => {
this.localNotification.requestPermission().then((perm) => {
if (perm === 'granted') this.permissionGranted = true;
});
});
}
}
tab = 'location';
notifications = [];
center = {
lat: 6.435838,
lng: 3.451384,
};
permissionGranted = false;
address = '';
reverseGeocode(latLng) {
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: latLng }, (results, status) => {
if (status === 'OK') {
if (results[0]) {
const address = results[0].formatted_address;
const addressList = address.split(',');
this.address = addressList[0];
}
}
});
}
ngOnInit() {
const locationChannel = this.pusher.init('location');
const geofenceChannel = this.pusher.init('location');
locationChannel.bind('ping', (data) => {
this.center = {
...data,
};
});
geofenceChannel.bind('exit', (data) => {
this.reverseGeocode(data);
if (this.permissionGranted) {
this.localNotification.create('Geofence exited', {
tag: 'message1',
body: 'User has exited the defined geofence',
icon: 'assets/imgs/user.svg',
});
}
const notification = {
header: 'User has exited the geofence',
body: `Current location: ${this.address}`,
};
this.notifications = this.notifications.concat(notification);
});
}
}
The admin component will be displaying notifications in the notifications bar whenever a user leaves the set geofence. We’ll also have a notifications segment that will show a list of notifications over time.
To display notifications in the notifications bar, we’ll be using the PhonegapLocalNotification native API. Install it as a plugin by running the following commands.
ionic cordova plugin add phonegap-plugin-local-notification
npm install --save @ionic-native/phonegap-local-notification
In the ngOnInit
lifecycle, we list for two events ping
and exit
from Pusher. In the ping
event callback we use the data returned to update the current position of the marker. When the exit
event is called we trigger a notification using the PhonegapLocalNotification
. A notification object is created containing a reverse geocoded address and appended to the notifications array.
After creating the admin
page, the next step is to register it in the app.module.ts
file and then add it to the tabs
page. Update the app.module.ts
file and the tabs
page to include the admin
page and the PhonegapLocalNotification
provider.
// src/app/app.module.ts
...
import { AdminPage } from '../pages/admin/admin';
import { PhonegapLocalNotification } from '@ionic-native/phonegap-local-notification';
@NgModule({
declarations: [
...
AdminPage
],
...
providers: [
...
PhonegapLocalNotification,
],
})
export class AppModule {}
Then update the TabsPage to Include the AdminPage. Open the tabs.html
file in the src/pages/tabs
directory and replace the content with the code below:
<!-- tab.html -->
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="Home" tabIcon="home"></ion-tab>
<ion-tab [root]="tab2Root" tabTitle="Admin" tabIcon="people"></ion-tab>
</ion-tabs>
Next update the tabs.ts
file and include the AdminPage:
import { Component } from '@angular/core';
import { AdminPage } from '../admin/admin';
import { HomePage } from '../home/home';
@Component({
templateUrl: 'tabs.html',
})
export class TabsPage {
tab1Root = HomePage;
tab2Root = AdminPage;
constructor() {}
}
To test the application on your mobile device, download the IonicDevApp on your mobile device. Make sure your computer and your mobile device are connected to the same network. When you open the IonicDevApp, you should see Ionic apps running on your network listed.
To view the application, click on it and you should see a similar view with what was on the browser. Sending messages to the server might have worked on the browser but localhost doesn’t exist on your phone, so we’ll need to create a proxy to be able to send messages from mobile.
Using Ngrok as a proxy
To create a proxy for our server, we’ll download Ngrok. Visit the download page on the Ngrok website. Download the client for your OS. Unzip it and run the following command in the folder where Ngrok can be found:
./ngrok http 4000
Copy the forwarding url with https
and place it in the home.ts
file that previously had http://localhost:4000/ping
and http://localhost:4000/notify
. Please do not copy mine from the screenshot above.
// src/pages/home/home.ts
...
export class HomePage implements OnInit {
...
pingLocation() {
this.http
.post('<NGROK_URL>/ping', data)
.subscribe((res: Message) => {});
}
notify(location) {
this.http
.post('<NGROK_URL>/notify', location)
.subscribe((res) => {});
}
...
}
...
Ensure to include the forwarding url you copied where the placeholder string is
Running on an emulator
Since our application is making use of some native APIs, we’ll need to test it on an emulator. To run our app on an emulator, run any of the following commands depending on the platform you’re building for.
// for IOS
ionic cordova emulate ios
// for android
ionic cordova emulate android
To build your application to deploy on either the AppStore or PlayStore, follow the instructions found here.
Conclusion
Using Ionic, Google Maps and Pusher, we’ve been able to create an application that offers realtime location tracking. We’ve also learnt how to create geofences using native APIs. You can view the source code for the demo here.
29 June 2018
by Christian Nwamba