background geolocation image

Nativescript Geolocation in the background for Android

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

How to 10x your Android app speed!

When I started in Android and iOS app development I came from a web development environment and this both benefits and halts the creation of mobile apps. You can take your same design, creativity and basic layout skills across but when it comes to native mobile specific details this does not cross. 

When I started I also did not have time to learn the fundamentals as project deadlines took priority.

One of the core fundamentals I came to learn was the GPU overdraw feature in the android dev tools. This turned out to be the greatest find and a massive boost to our app at the time. At the time we had a successful non-laggy iOS app but with the same code and styles our android app was struggling, big time!

After some research (playing around) in the android dev tools I came across GPU overdraw when selecting the “show on screen” option I was greeted with a whole new world of green, blue and a lot of red! If you know anything about GPU overdraw red is not good.

Lets take a look into what this is showing:

android-overdraw-device

To explain:

android-overdraw-colours

The easiest way I found to visualise this is as follows. Imagine the app’s design as a cake with many layers, for each background or border you add to an element you add another layer to the cake. Now normally have many layers of cake is great! But like I said the app world is like the 4th dimension, a totally different world! In app development having many layers is not good and results in lower performance.

The phone has to re-draw all of the layers when scrolling and loading the app. When scrolling with many layers the app feels laggy as if you are dragging it through mud.  The app is redrawing all the layers for every pixel you are scrolling. Having to redraw multiple requires more resource. So the more layers you have, the laggier the app becomes. Simple, right!?

So how do I fix this, you ask?

Well, great question! The simple answer is: Reduce any unneeded background colours or borders.

If the background of your app is white and you are setting another layer on top of it to have a white background you are doing double the work. remove that extra white background on top. if you are only using a border bottom or only using a border top replace it with a StackLayout with a background of 1px height to create the same effect but only effecting 1px instead of the entire height of the element.

Add to cart