React-native-geolocation-service: Location doesn't update when mobile in background or screen locked?

Created on 3 Aug 2019  路  24Comments  路  Source: Agontuk/react-native-geolocation-service

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

Most helpful comment

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

All 24 comments

Thanks for suggestion @oakis

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

Thank you @Agontuk

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?

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);
   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 how to implement it, could u show sample snippet codes?

@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!

Works, but getCurrentPosition return the same value always

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

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

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 Thanks for your support

@felansu Thanks for your support

Please, test and tell me if works with you 馃暫

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.

Hello @ramkiakil anything update?

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

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

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

@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?

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

@felansu Thanks I will try

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lyseiha picture lyseiha  路  6Comments

Agontuk picture Agontuk  路  5Comments

rodrigofbm picture rodrigofbm  路  9Comments

Chepkeitany picture Chepkeitany  路  7Comments

matthieupinte picture matthieupinte  路  6Comments