Search code examples
androidangulargeolocationnativescriptnativescript-angular

How to make geolocation background services work in my nativescript-angular app?


I'm using this geolocation-plugin (https://github.com/NativeScript/nativescript-geolocation/tree/e125998c07ced42a215aca1a8feac1b7dbe7b6d6) to get constant location updates while app is in the background.

I tried using the code in the demo, but I'm not figuring out how to make it work.

I would like the user to press a button and the app to start monitoring his location, even if the app goes in the background.

home.component.ts

import { Component, OnInit, AfterViewInit } from "@angular/core";
import { RadSideDrawer } from "nativescript-ui-sidedrawer";
import { getRootView } from "tns-core-modules/application";
import * as app from "tns-core-modules/application";
const utils = require("tns-core-modules/utils/utils");
import { device } from "tns-core-modules/platform";


//import { GeolocationService } from "../services/geolocation/geolocation.service";


@Component({
    selector: "Home",
    moduleId: module.id,
    templateUrl: "./home.component.html"
})
export class HomeComponent implements OnInit {

    public drawer: RadSideDrawer;
    public buttonText = "Start";

    locations = [];
    watchIds = [];
    jobId = 308;

    constructor(
        public geolocationService: GeolocationService,
    ) {}

    ngOnInit(): void {
        app.on(app.exitEvent, this._stopBackgroundJob);
    }


    ngAfterViewInit() {
        this.drawer = <RadSideDrawer>getRootView();
        this.drawer.gesturesEnabled = true;
    }

    onDrawerButtonTap(): void {
        const sideDrawer = <RadSideDrawer>app.getRootView();
        sideDrawer.showDrawer();
    }


    _stopBackgroundJob() {
        if (app.android) {
            let context = utils.ad.getApplicationContext();
            const jobScheduler = context.getSystemService((<any>android.content.Context).JOB_SCHEDULER_SERVICE);
            if (jobScheduler.getPendingJob(this.jobId) !== null) {
                jobScheduler.cancel(this.jobId);
                console.log(`Job Canceled: ${this.jobId}`);
            }
        }
    }

    checkLoc(){
        this.geolocationService.buttonGetLocationTap()
    }


    _onGeolocationService(){
        this.geolocationService.enableLocation()
        .then(result => {
            this.geolocationService.monitorLocation();

            if(this.geolocationService.locationMonitor === true){
                this.buttonText = "Stop";
            } else {
                this.buttonText = "Start";
            }

        })
    }

    public startBackgroundTap() {
        if (app.android) {
            let context = utils.ad.getApplicationContext();
            if (device.sdkVersion >= "26") {
                const jobScheduler = context.getSystemService((<any>android.content.Context).JOB_SCHEDULER_SERVICE);
                const component = new android.content.ComponentName(context, com.nativescript.location.BackgroundService26.class);
                const builder = new (<any>android.app).job.JobInfo.Builder(this.jobId, component);
                builder.setOverrideDeadline(0);
                return jobScheduler.schedule(builder.build());
            } else {
                let intent = new android.content.Intent(context, com.nativescript.location.BackgroundService.class);
                context.startService(intent);
            }
        }
    }

    public stopBackgroundTap() {
        if (app.android) {
            if (device.sdkVersion >= "26") {
                this._stopBackgroundJob();
            } else {
                let context = utils.ad.getApplicationContext();
                let intent = new android.content.Intent(context, com.nativescript.location.BackgroundService.class);
                context.stopService(intent);
            }
        }
    }

}

geolocation.service.ts

import { Injectable } from '@angular/core';
import * as geolocation from "nativescript-geolocation";
import { Accuracy } from "tns-core-modules/ui/enums";

@Injectable({
  providedIn: 'root'
})
export class GeolocationService {

  locations= [];
  watchIds = [];

  constructor() { }

  public enableLocation() {
    return geolocation.enableLocationRequest();
  }

  public checkIfLocationIsEnabled() {
    geolocation.isEnabled()
    .then(function (isEnabled) {
      if(isEnabled) {
        return true;
      } else {
        return false;
      }
    })
  }

  public buttonGetLocationTap() {
    let that = this;
    geolocation.getCurrentLocation({
        desiredAccuracy: Accuracy.high,
        iosAllowsBackgroundLocationUpdates: true,
        iosPausesLocationUpdatesAutomatically: false,
        iosOpenSettingsIfLocationHasBeenDenied: true,
        maximumAge: 5000,
        timeout: 10000,
        updateTime: 10000,
    }).then(function (loc) {
      console.log(loc["latitude"], loc["longitude"])  
      if (loc) {
            that.locations.push(loc);
        }
    }, function (e) {
        console.log("Error: " + (e.message || e));
    });
}

  public buttonStartTap() {
      try {
          let that = this;
          that.watchIds.push(geolocation.watchLocation(
              function (loc) {
                  if (loc) {
                      that.locations.push(loc);
                  }
              },
              function (e) {
                  console.log("Error: " + e.message);
              },
              {
                  desiredAccuracy: Accuracy.high,
                  iosAllowsBackgroundLocationUpdates: true,
                  iosPausesLocationUpdatesAutomatically: false,
                  iosOpenSettingsIfLocationHasBeenDenied: true,
                  maximumAge: 7000,
                  timeout: 9000,
                  updateTime: 9000,
              }));
      } catch (ex) {
          console.log("Error: " + ex.message);
      }
  }

  public buttonStopTap() {
    let watchId = this.watchIds.pop();
    while (watchId != null) {
        geolocation.clearWatch(watchId);
        watchId = this.watchIds.pop();
    }
  }


  public buttonClearTap() {
    this.locations.splice(0, this.locations.length);
  }
}

background.service.ts

import * as geolocation from "nativescript-geolocation";
import { Accuracy } from "tns-core-modules/ui/enums";
import * as application from "tns-core-modules/application";
import { device } from "tns-core-modules/platform";

let watchId;

function _clearWatch() {
    if (watchId) {
        geolocation.clearWatch(watchId);
        watchId = null;
    }
}

function _startWatch() {
    geolocation.enableLocationRequest().then(function () {
        _clearWatch();
        watchId = geolocation.watchLocation(
            function (loc) {
                if (loc) {
                    console.log('Background Location: ' + loc.latitude + ' ' + loc.longitude);
                }
            },
            function (e) {
                console.log("Background watchLocation error: " + (e.message || e));
            },
            {
                desiredAccuracy: Accuracy.high,
                updateDistance: 1.0,
                updateTime: 3000,
                minimumUpdateTime: 100
            });
    }, function (e) {
        console.log("Background enableLocationRequest error: " + (e.message || e));
    });
}

application.on(application.exitEvent, _clearWatch);

if (application.android) {
    if (device.sdkVersion < "26") {
        (<any>android.app.Service).extend("com.nativescript.location.BackgroundService", {
            onStartCommand: function (intent, flags, startId) {
                this.super.onStartCommand(intent, flags, startId);
                return android.app.Service.START_STICKY;
            },
            onCreate: function () {
                _startWatch();
            },
            onBind: function (intent) {
                console.log("on Bind Services");
            },
            onUnbind: function (intent) {
                console.log('UnBind Service');
            },
            onDestroy: function () {
                console.log('service onDestroy');
                _clearWatch();
            }
        });
    } else {
        (<any>android.app).job.JobService.extend("com.nativescript.location.BackgroundService26", {
            onStartJob() {
                console.log('service onStartJob');
                _startWatch();
                return true;
            },
            onStopJob(jobParameters: any) {
                console.log('service onStopJob');
                this.jobFinished(jobParameters, false);
                _clearWatch();
                return false;
            },
        });
    }
}

Firstly, there is a problem with the 'com' namespace as it says "Property 'nativescript' does not exist on type 'typeof com' ". The problem appears in the home.component.ts on these 3 lines:

const component = new android.content.ComponentName(context, com.nativescript.location.BackgroundService26.class); let intent = new android.content.Intent(context, com.nativescript.location.BackgroundService.class); let intent = new android.content.Intent(context, com.nativescript.location.BackgroundService.class);

Secondly, I don't know what is the purpose of background-service as it i not used anywhere in the demo (or maybe it is, but in a way that I'm not familiar with).

Thank you!


Solution

  • "Property 'nativescript' does not exist on type 'typeof com' "

    It's simple TypeScript declaration error, casting it to any may solve your issue.

    const component = new android.content.ComponentName(context, (com as any).nativescript.location.BackgroundService26.class); 
    let intent = new android.content.Intent(context, (com as any).nativescript.location.BackgroundService.class); 
    let intent = new android.content.Intent(context, (com as any).nativescript.location.BackgroundService.class);
    

    As you could see in the demo app, declare var com: any; serves the same purpose. Alternatively you may declare proper typings if you are familiar with TypeScript.

    Secondly, I don't know what is the purpose of background-service

    I think we discussed this already in a different thread, background service is used when you want to run some tasks even when your app is not running.

    The class you are using above com.nativescript.location.BackgroundService is from that file (background-service.ts). The same class should be declared in AndroidManifest.xml too.

    Edit:

    Sounds like you didn't update your webpack.config.js with entry of background-service.ts file.

    // Add your custom Activities, Services and other Android app components here.
    const appComponents = [
        "tns-core-modules/ui/frame",
        "tns-core-modules/ui/frame/activity",
         resolve(__dirname, "src/path/to/background-service"),
    ];