Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

watchLocation method doesn't get location in background mode android #256

Open
bhoomiJagani opened this issue Nov 21, 2019 · 22 comments
Open

Comments

@bhoomiJagani
Copy link

Which platform(s) does your issue occur on?
Android
Android 10
Device Moto G(2nd Generation)

Please, provide the following version numbers that your issue occurs with:
CLI: 8.3.18

In, package.json
"nativescript-geolocation": "^5.1.0",

Please, tell us how to recreate the issue in as much detail as possible.

I'm using Geolocation apis to get location in background in android. When I call watchLocation in foreground, it's giving location, but when app goes to background, I didn't get any location. I am displaying location in console.log. For ios, when app goes in background mode, it's display log of location. I am using nativescript with angular.

Is there any code involved?

public buttonStartTap() {
   console.log("buttonStartTap");
   try {
     let that = this;
     this.watchIds.push(geolocation.watchLocation(
       function (loc) {
         if (loc) {
           that.locations.push(loc);
           console.log("loc", loc);
         }
       },
       function (e) {
         console.log("Error: " + e.message);
       },
       {
         desiredAccuracy: Accuracy.high,
         updateDistance: 1,
         updateTime: 3000,
         minimumUpdateTime: 100,
         iosAllowsBackgroundLocationUpdates: true,
         iosPausesLocationUpdatesAutomatically: false,

       }));
   } catch (ex) {
     console.log("Error: " + ex.message);
   }
 }
@butaminas
Copy link

I had a similar problem. It is not directly related to nativescript-geolocation rather to Android itself. You have to create a foreground service in order to track geolocation while the app is in the foreground.

Read more here: https://developer.android.com/about/versions/oreo/background-location-limits#tuning-behavior

@akarsh014
Copy link

I still the same issue, i get only few coordinates after which "service onStopJob" is called

@butaminas
Copy link

I still the same issue, i get only few coordinates after which "service onStopJob" is called

My initial fix seems to only fix this problem on Xiaomi devices. I've tried it on pure Android (like Samsung phones) and it would stop working after few seconds.

@butaminas
Copy link

I think I found the solution!!!
Hopefully, more can test this and confirm.

What I did was I added android:foregroundServiceType="location" to my ForegroundService Service in Android.xml

My final service tag looks like this:

        <service
                android:name="your-name-here"
                android:foregroundServiceType="location"
                android:stopWithTask="true"
                android:enabled="true"
                android:exported="false" />

My minSdkVersion is 21

Hope it helps someone as this was really painful to me... 🤯

@JuanDeLeon
Copy link

JuanDeLeon commented May 1, 2020

Background still not working for me on android, Nexus 5X API 28 emulator.
Pulled the Vue demo, and then followed the instructions to add the android service.
The service starts tracking correctly and keeps working as long as the app is in foreground.

I can see the logs on console:
JS: 'Background Location: 37.3521938 -122.0620458'
JS: 'Background Location: 37.3521957 -122.0620825'
JS: 'Background Location: 37.352197 -122.0621304'

But as soon as I send it to the background, the Geolocation icon at the top of the phone is gone, and it stops logging my location.

I have added the following to Android Manifest:

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

<!-- This is at application level: -->
<service android:name="com.nativescript.location.BackgroundService" 
	android:exported="false" >
</service>

<service android:name="com.nativescript.location.BackgroundService26" 
	android:permission="android.permission.BIND_JOB_SERVICE"
            android:enabled="true"
            android:exported="false"
            android:stopWithTask="true"
            android:foregroundServiceType="location">
</service>

Any ideas of what could be missing? Thanks in advance.

Edit: Background service actually seems to work fine when ran on my Samsung Galaxy tablet though. Might be something specific with the simulator...

@PlayNext
Copy link

PlayNext commented Jul 3, 2020

What I did was I added android:foregroundServiceType="location" to my ForegroundService Service in Android.xml

Confirmed on Samsung Android 10. Thank you butaminas!
It should be mentioned also you need to add compileSdkVersion 29 to your app.gradle for this to work.

@dlcole
Copy link

dlcole commented Feb 3, 2021

I'm working through this issue presently. The guide suggested by @Aceman18 looks pertinent, 'tho it's an Angular example and my project Javascript. So far I've not been successful in even getting a clean build, even though I've done lots of such conversions over the years. If anyone has a working version in Javascript I'd love to connect with you.

[Edit]

I have this working nicely now in a JavaScript project. Here's the changes I made:

Create a new file, foreground-service.android.js in the /app folder:

/**
 * Provide Android foreground service for nativescript-geolocation.watchLocation() 
 * @see https://appnative.org/nativescript-geolocation-in-the-background-for-android/
 * @see https://github.com/NativeScript/NativeScript/issues/3422
 */

android.app.Service.extend('org.nativescript.geolocation.ForegroundService', {

  onStartCommand: function (intent, flags, startId) {
    this.super.onStartCommand(intent, flags, startId);
    return android.app.Service.START_STICKY;
  },

  onCreate: function () {
    this.super.onCreate();
    this.startForeground(1, this.getNotification());
  },

  onBind: function (intent) {
    return this.super.onBind(intent);
  },

  onUnbind: function (intent) {
    return this.super.onUnbind(intent);
  },

  onDestroy: function () {
    this.stopForeground(true);
  },

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

    return builder.build();
  },
});

Add the permission in AndroidManifest.xml

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

And define the service in AndroidManifest.xml

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

Resolve the path in webpack.config.js:

appComponents.push(...[
        "tns-core-modules/ui/frame",
        "tns-core-modules/ui/frame/activity",
        resolve(__dirname, "./app/foreground-service.android.js"), // <-- this is the addition
    ]);

Set up the foreground service in app.js:

// Set up foreground service for Android background location 

startForegroundService();
application.on(application.exitEvent, () => {
  stopForegroundService();
});

function startForegroundService() {
  const context = utils.ad.getApplicationContext();
  const intent = new android.content.Intent();
  intent.setClassName(context, 'org.nativescript.geolocation.ForegroundService');
  context.startForegroundService(intent);
}

function stopForegroundService() {
  const context = utils.ad.getApplicationContext();
  const intent = new android.content.Intent();
  intent.setClassName(context, 'org.nativescript.geolocation.ForegroundService');
  context.stopService(intent);
}

And, if you're not already including the utils plugin, add

const utils = require("tns-core-modules/utils/utils");

Based on the comments in this issue, I also set compileSdkVersion in app.gradle:

android {
compileSdkVersion 29 // <- this was added
defaultConfig {

@dlcole
Copy link

dlcole commented Feb 27, 2022

Referencing the above post, for webpack 5, you'll need to adjust webpack.config.js to something like this:

const webpack = require("@nativescript/webpack");

module.exports = (env) => {

  // See https://stackoverflow.com/questions/70428159/nativescript-8-1-migration-adding-external-to-webpack-config-js
  env.appComponents = (env.appComponents || []).concat(['./foreground-service.android'])
	
  webpack.init(env);

	// Learn how to customize:
	// https://docs.nativescript.org/webpack

	return webpack.resolveConfig();
};

@nmandyam
Copy link

nmandyam commented Aug 4, 2022

@dlcole Thank you for the code, that's very helpful - and it works. But unfortunately, this is not enough for a newbie like me. I'm unable to figure out where in your code the response to watchLocation happens. Like for everyone else, watchLocation works well when my app is in the foreground, not all when it is not. I have your code included and compiled, the foreground service starts (I have console-writes to tell me that). But then what? I need to write out the locations - where do I do that from, in your code?

Related: the location needs to be tracked even if the user swipes away the app without logging out - I'm hoping the foreground service will handle that?

Last question: Any idea how this would work on iOS?

Sorry if all this sounds dumb! All help appreciated!

@dlcole
Copy link

dlcole commented Aug 4, 2022

@nmandyam Your watchLocation function should receive control from the foreground service, just as it does when your app itself is in the foreground. This should work while your app is in the background on Android, but won't work if your app is terminated. (You can take some extra steps to also make it work when your app is terminated - see this article.)

iOS works differently, but doesn't require additional services. In Xcode under Signing and Capabilities -> Background Modes, you'll need to check "Location Updates".

In your Info.plist file, you'll need the entries:

  <key>UIBackgroundModes</key>
    <array>
      <string>location</string>
    </array>

and also

  <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
  <string>This application requires location services to show your position on maps.</string>
  <key>NSLocationWhenInUseUsageDescription</key>
  <string>This application requires location services to show your position on maps.</string>

Good luck!

@nmandyam
Copy link

nmandyam commented Aug 5, 2022

@dlcole thank you!

@nmandyam
Copy link

nmandyam commented Aug 9, 2022

@dlcole sorry to bother you about this again, but I'm completely lost. As it stands now:

  • I added your Foreground Service code to my app
  • I invoke the watchLocation on a particular event happening (user clicks on a button in my app).

So it starts tracking location all OK when the app is in the foreground. And it continues to track location when the app goes to the background. I see the Android notification that the foreground service is running. But when I swipe away the app, the location stops being tracked, like you said would happen.

BUT: Per that article that you linked me to, it's the foreground service that makes sure that my app continues to report location when swiped away. And the Java code is nearly identical to your snippet above, so I assume the "foreground service" that the article refers to and the "foreground service" that you've helped me build are the same thing. Therefore, by implementing the foreground service, the app should continue reporting location. Yet, swipe away the app and it won't track location (just like you said it won't). What am I missing?

Thanks for all your help.

@dlcole
Copy link

dlcole commented Aug 9, 2022

When you say, "swiped away" do you mean that you've actually terminated the app? This is a key distinction.

@nmandyam
Copy link

nmandyam commented Aug 9, 2022 via email

@dlcole
Copy link

dlcole commented Aug 9, 2022

So when you open the list of Recent apps, your app is no longer there, yes? If so, this means your app is actually terminated and not just in the background. The article I linked to in a prior posts suggests how to make this scenario work, but my code only works when the app is in the foreground (normal case) or background, using the foreground service (I admit the terminology is confusing).

@shradovyy
Copy link

@dlcole I managed to get it working with foreground service, the only problem is after some time location updates stop receiving for some reason. Do you happen to know why? let me know if you can assist

@dlcole
Copy link

dlcole commented Mar 28, 2023

@shradovyy I haven't see this behavior but I've not tested this code recently. How long does it take to stop receiving updates?

@shradovyy
Copy link

It really depends. It might work for as long as I don't open any other apps and keep the screen on (do not lock the phone). As soon as I open other app it seems like android suspends the app even when foreground service is running.

When I open the app again from the recent apps it kind of restarts (splash screen is shown)

@shradovyy
Copy link

@dlcole by the way, thank you for a quick reply

@dlcole
Copy link

dlcole commented Mar 28, 2023 via email

@dlcole
Copy link

dlcole commented Mar 30, 2023

So I tested this back on Tuesday and the Android phone continued to send it's location data. BUT, the phone may need to be moving for that to happen, depending on what you specify in your watchLocation options:

{  // options
  desiredAccuracy: Accuracy.high,
  updateDistance: 100,
  updateTime: 30000, // ignored on iOS 
  minimumUpdateTime: 10000, // ignored on iOS 
  iosAllowsBackgroundLocationUpdates: true,
}

I noticed that even with the above specified, I was only receiving location updates from Android when the phone was moving.

@ignacio68
Copy link

ignacio68 commented Sep 7, 2023

Thanks @dlcole , it works fine in background in android.

This is the code in Typescript:

`@NativeClass()
@javaproxy("org.nativescript.geolocation.ForegroundService")
class ForegroundService extends android.app.Service {
onStartCommand(intent: android.content.Intent, flags: number, startId: number) {
super.onStartCommand(intent, flags, startId);
return android.app.Service.START_STICKY;
}

onCreate() {
super.onCreate();
console.log("FOREGROUND SERVICE CREATED!");
this.startForeground(1, this.getNotification());
}

onBind(intent: android.content.Intent) {
return super.onBind(intent);
}

onUnbind(intent: android.content.Intent) {
return super.onUnbind(intent);
}

onDestroy() {
console.log("FOREGROUND SERVICE DESTROYED");
super.onDestroy();
this.stopForeground(true);
}

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);
notificationManager.createNotificationChannel(channel);
const builder = new android.app.Notification.Builder(this.getApplicationContext(), "channel_01");

return builder.build();

}
}`

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants