background geolocation image

Introduction for background geolocation

One question I see thrown around a lot is “How can I record a geolocation in the background?”. I see it in nativescript forums, stack overflow and on comments of blog posts.

I found no good resource that explained a modern approach to record background geolocation in nativescript especially since the Background Execution Limits that android implements in Android 8.0.

So this is the perfect guide! (Until android change their restrictions again)

By the end of reading this you will be able to record and receive locations when the app is in the background to use in whatever way you need.

Nativescript and Angular is the framework and flavour we will use for this tutorial. If you have not used Nativescript Angular before then use this guide to get started.

Outline

  • Android Foreground service
  • Nativescript Geolocation plugin

Creating a new app

Once you have the Nativescript setup complete open a new terminal window and enter.

tns create geolocation-app --template tns-template-blank-ng

Note: tns-template-blank-ng makes a blank nativescript app easier to display for this tutorial.

Once the app has finished setting up navigate into the new project and run the app.

cd geolocation-app
tns run android

Setting up Nativescript Geolocation

Install the geolocation plugin:

tns plugin add nativescript-geolocation

Request access to location:

Navigate to src/app/home.home.component.ts

Add imports for the geolocation plugin:

import * as geolocation from "nativescript-geolocation";

Within ngOnInit add the following:

geolocation.isEnabled().then((isEnabled) => {
    if (!isEnabled) {
        geolocation.enableLocationRequest(true, true).then(() => {
            console.log("User Enabled Location Service");
        }, (e) => {
            console.log("Error: " + (e.message || e));
        }).catch(ex => {
            console.log("Unable to Enable Location", ex);
        });
    }
}, (e) => {
    console.log("Error: " + (e.message || e));
});

The geolocation.isEnabled() initially checks whether the user has already accepted the popup so it will stop the function from re running blocking potential errors.

If geolocation is not enabled then the user will be prompted to accept triggered by the geolocation.enableLocationRequest function.

Add location watcher

Navigate to src/app/home/home.component.ts and add two new functions:

import { Accuracy } from "tns-core-modules/ui/enums";

.......
geolocationWatchId: number;
locations = [];

.......
// Geolocation tracking location start
locationTrackStart() {
    this.geolocationWatchId = geolocation.watchLocation(
        (loc) => {
            this.locations.push(loc)
        },
        (error) => {
            console.log("error: ", error)
        },
        {
            desiredAccuracy: Accuracy.high,
            updateDistance: 25,
            updateTime: 10000,
            minimumUpdateTime: 5000
        }
    );
}

// Clear geolocationWatchId to stop tracking
clearLocationTracker() {
    geolocation.clearWatch(this.geolocationWatchId);
}

The function locationTrackStart() is used to initially trigger the tracking of the device location. Once a successful location has been received we are pushing it to an array of locations. This will be used for displaying on the home.component.html

home.component.html

<ActionBar class="action-bar">
    <Label class="action-bar-title" text="Home"></Label>
</ActionBar>

<GridLayout class="page" rows="auto, *">
    <!-- Add your page content here -->
    <Button text="Start recording location" (tap)="locationTrackStart()"></Button>

    <ListView row="2" [items]="locations">
        <ng-template let-item="item">
            <Label text="{{ item.latitude + ', ' + item.longitude + ', ' + item.altitude }}" ></Label>
        </ng-template>
    </ListView>
</GridLayout>

Also its best to clear the location tracker to stop repeating loops. Here it is done on ngOnDestroy but you can handle it as you like.

ngOnDestroy() {        this.clearLocationTracker();    }

Now you can try it out!

Save the files and run the app. Accept location permissions and start tracking. It should look like this:

Geolocation app Homepage

Foreground Service

The foreground service the magic piece the makes background location tracking possible. It runs the app and keeps all js processes running which can include in our case the geolocation tracking.

Add a new file in the src/app folder called foreground-service.android.ts and add the following code:

@JavaProxy('org.nativescript.geolocation.ForegroundService')
class ForegroundService extends android.app.Service {
    onStartCommand(intent, flags, startId) {
        console.log('onStartCommand')
        super.onStartCommand(intent, flags, startId);
        return android.app.Service.START_STICKY;
    }

    onCreate() {
        console.log('onCreate')
        super.onCreate();
        this.startForeground(1, this.getNotification());
    }

    onBind(intent) {
        return super.onBind(intent);
    }

    onUnbind(intent) {
        return super.onUnbind(intent);
    }

    onDestroy() {
        console.log('onDestroy')
        this.stopForeground(true);
    }

    private getNotification() {
        const channel = new android.app.NotificationChannel(
            'channel_01',
            'ForegroundService Channel',
            android.app.NotificationManager.IMPORTANCE_DEFAULT
        );
        const notificationManager = this.getSystemService(android.content.Context.NOTIFICATION_SERVICE) as android.app.NotificationManager;
        notificationManager.createNotificationChannel(channel);
        const builder = new android.app.Notification.Builder(this.getApplicationContext(), 'channel_01');

        return builder.build();
    }
}

This is the foreground service but we need to initiate this.
Go to app.component.ts and add the following:

constructor() {
    app.on(app.exitEvent, () => {
        this.stopForegroundService();
    });
    this.startForegroundService();
}

private startForegroundService() {
    const context = app.android.context;
    const intent = new android.content.Intent();
    intent.setClassName(context, 'org.nativescript.geolocation.ForegroundService');
    context.startForegroundService(intent);
}

private stopForegroundService() {
    const context = app.android.context;
    const intent = new android.content.Intent();
    intent.setClassName(context, 'org.nativescript.geolocation.ForegroundService');
    context.stopService(intent);
}

You will need to import this:

import * as app from "tns-core-modules/application";

You may see errors on the “android.content.Intent()” saying “cannot find android”. This is because currently our codebase cannot understand the native platform types. We need to add Nativescript platform declarations so that we can correctly avoid these typings errors.

npm i tns-platform-declarations --save-dev

Next add a new file called references.d.ts in src/app and add the following lines:

/// <reference path="./../node_modules/tns-platform-declarations/ios.d.ts" />
/// <reference path="./../node_modules/tns-platform-declarations/android-26.d.ts" />

Next we add a service for the foreground service in our AndroidManifest.xml found in app_resources/android/src/main/ add this below the final <activity> inside the <application> tags

<service
android:name="org.nativescript.geolocation.ForegroundService"
    android:enabled="true"
    android:stopWithTask="true"
    android:exported="false" />

Also add permissions for using a foreground service:

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

Final step: Add custom service to the webpack config so it builds and runs correctly. Add this to your appComponents constant.

Before:
const appComponents = [
"tns-core-modules/ui/frame",
"tns-core-modules/ui/frame/activity",
];
After:
const appComponents = [
"tns-core-modules/ui/frame",
"tns-core-modules/ui/frame/activity",
resolve(__dirname, "./src/foreground-service.android")
];

Conclusion

Following these steps will give you background location tracking capabilities in a modern best practice approach. This screen shot shows that the location marker in the top bar is there so the locations are still tracking. You can even console.log out the locations as you track them to see it in action as it happens.
I hope this guide helps you achieve background location tracking using Nativescript Angular and Geolocation on Android.

The full version of this app is downloadable from our store here. Check out the rest of our blog and also checkout items in the shop if you want to see what other themes and apps we have on offer

Leave a Reply

Your email address will not be published. Required fields are marked *

Add to cart