NativeScript / nativescript-geolocation

Geolocation plugin to use for getting current location, monitor movement, etc
Apache License 2.0
139 stars 77 forks source link

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

Open bhoomiJagani opened 4 years ago

bhoomiJagani commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

butaminas commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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 commented 2 years ago

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 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

@dlcole thank you!

nmandyam commented 1 year ago

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

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 commented 1 year ago

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

nmandyam commented 1 year ago

Sorry, didn't know there was a distinction. I go to the list of Recent apps, swipe to the next or right to get to my app, swipe the app away. I don't log out, I don't terminate it from the System screen. Does that make sense?

On Tue, 9 Aug, 2022, 18:58 David Cole, @.***> wrote:

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

— Reply to this email directly, view it on GitHub https://github.com/NativeScript/nativescript-geolocation/issues/256#issuecomment-1209382687, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADAOCKLW4QACWOWGGNRGJPLVYJMGRANCNFSM4JP675HQ . You are receiving this because you were mentioned.Message ID: @.***>

dlcole commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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

dlcole commented 1 year ago

OK, I’ll test this later today. Sent from my iPhoneOn Mar 28, 2023, at 11:11 AM, shradovyy @.***> wrote:

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)

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

dlcole commented 1 year ago

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 commented 10 months ago

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();

} }`