react-native-geolocation-service icon indicating copy to clipboard operation
react-native-geolocation-service copied to clipboard

Location doesn't update when mobile in background or screen locked?

Open ramkiakil opened this issue 4 years ago • 34 comments

Location doesn't update in watch position.location updating in foreground only.it's stop location tracking when app goes to background in Android? Track only when in foreground. enableHighAccuracy:true,showLocationDialog:false,interval:5000,fastestInterval:2000,distanceFilter:2

ramkiakil avatar Aug 03 '19 08:08 ramkiakil

https://github.com/Agontuk/react-native-geolocation-service/issues/102 https://github.com/Agontuk/react-native-geolocation-service/issues/99 https://github.com/Agontuk/react-native-geolocation-service/issues/80

oakis avatar Aug 03 '19 11:08 oakis

Thanks for suggestion @oakis

ramkiakil avatar Aug 05 '19 12:08 ramkiakil

This library does not track the state of the application. You should use Headless JS for doing background work and call geolocation API there.

Agontuk avatar Aug 09 '19 19:08 Agontuk

Thank you @Agontuk

ramkey-akil avatar Aug 17 '19 11:08 ramkey-akil

Would it work if I use BackgroundTimer library?
Right now I'm using @react-native-community/geolocation but I sometimes I get the timeout error, I found a configuration that works but when the app goes to the background or the device is locked I start getting the timeout error again until I open the app.

I would like to know if someone is having this issue?

HugoLiconV avatar Jan 07 '20 23:01 HugoLiconV

This library does not track the state of the application. You should use Headless JS for doing background work and call geolocation API there.

Say we have a watchPosition() in our App.js and it gives us locations when app is in foreground

We want to watch positions even in background or terminated state What do we need to do in our headless task ? Do we need another watch statement like the following?

   let backgroundGeo = async (event) => {
     Geolocation.watchPosition() // yet another watch? 
   }
AppRegistry.registerHeadlessTask('backgroundGeo', () => backgroundGeo);

littlehome-eugene avatar Feb 07 '20 05:02 littlehome-eugene

   let backgroundGeo = async (event) => {
     Geolocation.watchPosition() // yet another watch? 
   }
AppRegistry.registerHeadlessTask('backgroundGeo', () => backgroundGeo);

You should just implement it once inside HeadlessTask and use that in the UI.

Agontuk avatar Mar 16 '20 15:03 Agontuk

@Agontuk how to implement it, could u show sample snippet codes?

lyseiha avatar Jul 02 '20 05:07 lyseiha

@lyseiha i'm working in that now, i think if you create a headlessTask executed each minute for example, and in react native side you register headlesstask for tun Geolocation.getCurrentPosition works.

i'm going to test now!

felansu avatar Jul 02 '20 12:07 felansu

Works, but getCurrentPosition return the same value always

felansu avatar Jul 02 '20 13:07 felansu

in options, with maximumAge: 0 works fine, some data duplicated but works fine...

felansu avatar Jul 02 '20 14:07 felansu

@felansu how to implement it, share some code example?

ramkiakil avatar Jul 02 '20 17:07 ramkiakil

Sure!

index.js react-native

async function test1(data) {
  console.log('Test1 executed!')
  const token = data.token;

  const options = {
    maximumAge: 0,
    enableHighAccuracy: true,
    showLocationDialog: true,
    forceRequestLocation: true,
  };

  Geolocation.getCurrentPosition((position) => {
      const geoData = {...position.coords, timestamp: position.timestamp, mocked: position.mocked};
      Geolocation.stopObserving();
      axios.post(API_APP_BASE_URL + 'userGeolocation/create', {geoData, token})
        .then(() => {
          console.log('saved')
        })
        .catch(error => {
          console.log('error', error)
        });
    },
    (error) => {
      console.log(error.code, error.message);
    },
    options
  );
}

AppRegistry.registerHeadlessTask('UserGeolocation', () => test1);

In java

image

Package

public class UserGeolocationPackage implements ReactPackage {

    @NotNull
    @Override
    public List<NativeModule> createNativeModules(@NotNull ReactApplicationContext reactContext) {
        return Collections.singletonList(new UserGeolocationModule(reactContext));
    }

    @NotNull
    @Override
    public List<ViewManager> createViewManagers(@NotNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

Module

public class UserGeolocationModule extends ReactContextBaseJavaModule {
    private static final String REACT_CLASS = "UserGeolocation";
    private static ReactApplicationContext reactContext;

    UserGeolocationModule(@Nonnull ReactApplicationContext reactContext) {
        super(reactContext);
        UserGeolocationModule.reactContext = reactContext;
    }

    @Nonnull
    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @ReactMethod
    public void startService(String token, int delayMillis) {
        Intent intent = new Intent(reactContext, UserGeolocationService.class);
        intent.putExtra("token", token);
        intent.putExtra("delayMillis", delayMillis);
        reactContext.startService(intent);
    }
}

The service:

public class UserGeolocationService extends Service {
    private static final String CHANNEL_ID = "USERGEOLOCATION";
    private static final int SERVICE_NOTIFICATION_ID = 101010;
    private static String token;
    private static int delayMillis;

    private Handler handler = new Handler();

    private Runnable runnableCode = new Runnable() {
        public void run() {
            Context context = getApplicationContext();
            Intent intent = new Intent(context, UserGeolocationEventService.class);

            if (token != null) {
                intent.putExtra("token", token);
            }

            context.startService(intent);
            HeadlessJsTaskService.acquireWakeLockNow(context);
            handler.postDelayed(this, delayMillis);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.handler.removeCallbacks(this.runnableCode);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Bundle extras = intent.getExtras();

        if (extras != null) {
            token = extras.getString("token");
            delayMillis = extras.getInt("delayMillis");
        }

        this.handler.post(this.runnableCode);
        createNotificationChannel();


        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Felansu Project")
                .setContentText("That works!")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(contentIntent)
                .setOngoing(true)
                .build();

        startForeground(SERVICE_NOTIFICATION_ID, notification);
        return START_STICKY;
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, importance);
            channel.setDescription("GPS data");
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

And the UserGeolocationEventService

public class UserGeolocationEventService extends HeadlessJsTaskService {
    @Nullable
    protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {
        Bundle extras = intent.getExtras();
        return new HeadlessJsTaskConfig(
                "UserGeolocation",
                extras != null ? Arguments.fromBundle(extras) : Arguments.createMap(),
                5000,
                true);
    }
}

And finally start the service from react native:

UserGeolocation.startService(token, GPS_DELAY_MILIS);

I put the example with token because i use that for get in test1 function, i dont need more for save the data. If you detect an error with that logic, please tell me.

Thanks!

felansu avatar Jul 02 '20 17:07 felansu

@felansu Thanks for your support

ramkiakil avatar Jul 02 '20 17:07 ramkiakil

@felansu Thanks for your support

Please, test and tell me if works with you 🕺

felansu avatar Jul 02 '20 17:07 felansu

Hello @felansu, Is it working in background and location also update? but yesterday i try it's show location lat and lng always the same number.

lyseiha avatar Jul 03 '20 04:07 lyseiha

Hello @ramkiakil anything update?

lyseiha avatar Aug 06 '20 09:08 lyseiha

I'm facing lot of issues,did you try this

ramkiakil avatar Aug 06 '20 09:08 ramkiakil

not yet trying it, but i will try it tonight. i am trying to do another thing but hmm alot of issues.

lyseiha avatar Aug 06 '20 09:08 lyseiha

@lyseiha Guys, my final solution has been create a ListenableWorker (that can be executed each >= 15 minutes). In my ReactContextBaseJavaModule i have:


import androidx.work.Data;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;

public class UserGeolocationModule extends ReactContextBaseJavaModule {
    private static final String REACT_CLASS = "UserGeolocation";
    private static ReactApplicationContext reactContext;

    UserGeolocationModule(@Nonnull ReactApplicationContext reactContext) {
        super(reactContext);
        UserGeolocationModule.reactContext = reactContext;
    }

    @Nonnull
    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @ReactMethod
    public void startWorker() {

        PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest
                .Builder(UserGeolocationWorker.class,
                PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
                TimeUnit.MILLISECONDS)
                .addTag("UserGeolocationWorker");

        Data.Builder data = new Data.Builder();
        data.putString("keyOfField", "youCanPutDataHere");
        builder.setInputData(data.build());

        PeriodicWorkRequest periodicWorkRequest = builder.build();

        WorkManager.getInstance(reactContext).enqueue(periodicWorkRequest);
    }
}

and in ListenableWorker have:

import android.content.Context;
import android.location.Location;
import android.os.HandlerThread;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.common.util.concurrent.ListenableFuture;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

public class UserGeolocationWorker extends ListenableWorker {
    private static final String TAG = "UserGeolocationWorker";

    private String keyOfField;

    private FusedLocationProviderClient mFusedLocationClient;

    private final HandlerThread handlerThread = new HandlerThread("RequestLocation");

    public UserGeolocationWorker(@NonNull final Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);

        this.keyOfField = workerParams.getInputData().getString("keyOfField");

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(appContext);
    }

    private LocationCallback fusedTrackerCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            try {
                Log.e(TAG, "onlocation result");

                if (locationResult.getLastLocation() != null) {
                    Log.e(TAG, "with get last location");
                    Location location = locationResult.getLastLocation();
                    Log.e(TAG, "executing");
                    new OrionAsyncTask().execute(url, getPostDataString(location));
                    Log.e(TAG, "executed");
                    mFusedLocationClient.removeLocationUpdates(fusedTrackerCallback);
                    Log.e(TAG, "removed");
                    handlerThread.quit();
                    Log.e(TAG, "quit");
                }
            } catch (UnsupportedEncodingException e) {
                Log.e(TAG, "error");
                e.printStackTrace();
            }
        }
    };

    @Override
    public ListenableFuture<Result> startWork() {
        LocationRequest locationRequest = new LocationRequest();
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setFastestInterval(2000);
        locationRequest.setInterval(2000);

        handlerThread.start();

        return CallbackToFutureAdapter.getFuture(completer -> {
            Runnable runnable = () -> {
                Log.e(TAG, "reuesting");
                mFusedLocationClient.requestLocationUpdates(locationRequest, fusedTrackerCallback, handlerThread.getLooper());
                Log.e(TAG, "completeing");
                completer.set(Result.success());
                Log.e(TAG, "finishing");
            };

            new Thread(runnable).start();
            return runnable;
        });
    }


    private String getPostDataString(Location location) throws UnsupportedEncodingException {
        HashMap<String, String> params = new HashMap<>();
        params.put("keyOfField", "otherValue");
        params.put("latitude", location.getLatitude() + "");
        params.put("longitude", location.getLongitude() + "");
        params.put("accuracy", location.getAccuracy() + "");

        StringBuilder result = new StringBuilder();
        boolean first = true;
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (first)
                first = false;
            else
                result.append("&");

            result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
            result.append("=");
            result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
        }

        return result.toString();
    }
}

felansu avatar Aug 06 '20 09:08 felansu

@lyseiha Guys, my final solution has been create a ListenableWorker (that can be executed each >= 15 minutes). In my ReactContextBaseJavaModule i have:


import androidx.work.Data;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;

public class UserGeolocationModule extends ReactContextBaseJavaModule {
    private static final String REACT_CLASS = "UserGeolocation";
    private static ReactApplicationContext reactContext;

    UserGeolocationModule(@Nonnull ReactApplicationContext reactContext) {
        super(reactContext);
        UserGeolocationModule.reactContext = reactContext;
    }

    @Nonnull
    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @ReactMethod
    public void startWorker() {

        PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest
                .Builder(UserGeolocationWorker.class,
                PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS,
                TimeUnit.MILLISECONDS)
                .addTag("UserGeolocationWorker");

        Data.Builder data = new Data.Builder();
        data.putString("keyOfField", "youCanPutDataHere");
        builder.setInputData(data.build());

        PeriodicWorkRequest periodicWorkRequest = builder.build();

        WorkManager.getInstance(reactContext).enqueue(periodicWorkRequest);
    }
}

Is it possible to trigger headless task everything 5 or 10 seconds or every 10 or 50m distance?

lyseiha avatar Aug 06 '20 09:08 lyseiha

with workers the minimum time for re-run is 15 minutes

felansu avatar Aug 06 '20 09:08 felansu

@felansu Thanks I will try

ramkiakil avatar Aug 06 '20 18:08 ramkiakil

I've updated the example project to show how to use foreground service with location, see if it helps you. You can find the relevant changes in this commit https://github.com/Agontuk/react-native-geolocation-service/commit/cb7b98ead8c3bde21071b256f36ff7644c5e376a.

Agontuk avatar Aug 11 '20 15:08 Agontuk

For someone still facing this. Make sure you've added ACCESS_BACKGROUND_LOCATION in your androidmanifest. and ask for it in app permissions.

SymntxHomendra51 avatar May 17 '22 13:05 SymntxHomendra51

@felansu @lyseiha Hey Iam unable to get it working. Where should be the headless task registered? In app.js or in any js file where I want to track. And is the java code provided in this thread should be included in react native app code as well or not. If yes where. And is there any workaround for tracking location every 3 or 5 mins. And Iam looking for a solution that works even when phone is locked or app is in background or killed or open. please anyone who knows any of these, try to address my concerns. Thanks

vijay13267 avatar Oct 19 '23 14:10 vijay13267

Where should be the headless task registered?

yes

felansu avatar Oct 20 '23 11:10 felansu

@felansu I didnt get it. There are few questions in asked by me @vijay13267. Could you pls answers for each of them seperately if you dont mind. Thanks

Vijay-CareAllianz avatar Oct 25 '23 10:10 Vijay-CareAllianz

Are these files required in react native project too?

Screenshot 2023-10-25 at 4 25 01 PM

If Yes, where to write them. in which path?

Thanks

Vijay-CareAllianz avatar Oct 25 '23 10:10 Vijay-CareAllianz

@Vijay-CareAllianz all i know 4 years ago is answered and published in this thread, today i don't use that dependency and as you understand I don't remember much about this, it has rained a lot since then, good luck

felansu avatar Oct 25 '23 11:10 felansu