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

Can't get userLocation to update consistently in background mode

Open jmcelroy99 opened this issue 2 years ago • 20 comments

Your Environment

  • Plugin version:4.11.0
  • Platform: iOS or Android: ios
  • OS version: 16.4
  • Device manufacturer / model: iphone 13
  • React Native version (react-native -v): 0.70.6
  • Plugin config
export default class FGLocationRetriever {
  static instance = null;
  static getInstance() {
    if (this.instance == null) {
      this.instance = new FGLocationRetriever();
    }
    return this.instance;
  }

  constructor() {
    this.userKey = null;
    this.keysToTrack = [];
    this.keyToPhone = {};
    this.allowedKeysToTrackMe = [];
    this._onTick = this._onTick.bind(this);
    this._onUserLocationChange = this._onUserLocationChange.bind(this);
    this.intervalHandler = null;
    this.initialized = false;
  }

  init() {
    this.userKey = null;
    this.keysToTrack = [];
    this.keyToPhone = {};
    this.allowedKeysToTrackMe = [];
    this.intervalHandler = null;
    this.locationTrackingOn = false;

    if (!this.initialized) {
      FGLocationTrackingService.getInstance().init();
      FGLocationTrackingService.getInstance().setOnLocationListener(
        this._onUserLocationChange
      );
    }

    this._loadDataLocaly();

    this.initialized = true;
  }

  async _saveDataLocaly() {
    const data = {
      userKey: this.userKey,
      keysToTrack: this.keysToTrack,
      allowedKeysToTrackMe: this.allowedKeysToTrackMe,
      keyToPhone: this.keyToPhone,
      locationTrackingOn: this.locationTrackingOn,
    };

    await AsyncStorage.setItem("FGLocationRetriever", JSON.stringify(data));
  }

  async _loadDataLocaly() {
    const data = await AsyncStorage.getItem("FGLocationRetriever");
    if (data) {
      const parsedData = JSON.parse(data);
      this.userKey = parsedData.userKey;
      this.keysToTrack = parsedData.keysToTrack;
      this.allowedKeysToTrackMe = parsedData.allowedKeysToTrackMe;
      this.keyToPhone = parsedData.keyToPhone;

      if (parsedData.locationTrackingOn) {
        this.startLocationTracking();
      } else if (!parsedData.locationTrackingOn) {
        this.stopLocationTracking();
      }
    }
  }

  async _removeDataLocaly() {
    await AsyncStorage.removeItem("FGLocationRetriever");
  }

  async setUserPhone(phone) {
    this.userKey = await this._getUserKey(phone);
    await this._saveDataLocaly();
  }

  _onUserLocationChange(location) {
    try {
      database()
        .ref(`locations/${this.userKey}`)
        .set({
          lat: location.latitude,
          long: location.longitude,
          date: moment().format("YYYY-MM-DD HH:mm:ss"),
        })
    } catch (error) {
      console.log(error);
    }
  }

  startLocationTracking() {
    FGLocationTrackingService.getInstance().startLocationTracking();

    this.locationTrackingOn = true;

    let ref = database().ref(`permissions/${this.userKey}`);
    ref.set(this.allowedKeysToTrackMe);

    this._saveDataLocaly();
  }

  stopLocationTracking() {
    FGLocationTrackingService.getInstance().stopLocationTracking();

    this.locationTrackingOn = false;

    let ref = database().ref(`permissions/${this.userKey}`);
    ref.set([]);

    this._saveDataLocaly();
  }

  async _onTick() {
    if (!this.locationTrackingOn) {
      this.onPhonesLocationsListener([]);
      return;
    }
    let locations = [];
    await Promise.all(
      this.keysToTrack.map(async (key) => {
        const ref = database().ref(`permissions/${key}`);
        const permissions = await ref.once("value");
        if (
          permissions.exists() &&
          (permissions.val().includes(this.userKey) || "*" in permissions)
        ) {
          const ref = database().ref(`locations/${key}`);
          const user = await ref.once("value");
          console.log(this.keyToPhone[key])
          if (user.exists() && this.keyToPhone[key]) {
            const userModel = user.val();
            userModel["phone"] = this.keyToPhone[key];
            locations = [...locations, userModel]
          }
        }
      })
    );

    if (this.onPhonesLocationsListener) {
      this.onPhonesLocationsListener(locations);
    }
  }

  startListeningToLocationUpdates() {
    if (this.intervalHandler != null) {
      clearInterval(this.intervalHandler);
    }
    this.intervalHandler = setInterval(this._onTick, 10000);
  }

  stopListeningToLocationUpdates() {
    clearInterval(this.intervalHandler);
    this.intervalHandler = null;
  }

  setPhonesToTrack(phones) {
    this.keysToTrack = Promise.all(phones.map(async (phone) => await this._getUserKey(phone)));

    this._saveDataLocaly();
  }

  async addPhoneToTrack(phone) {
    const key = await this._getUserKey(phone);

    if (!this.keysToTrack.includes(key)) {
      this.keysToTrack = [...this.keysToTrack, key];
    }

    this._saveDataLocaly();
  }

  async removePhoneToTrack(phone) {
    const key = await this._getUserKey(phone);

    if (this.keysToTrack.includes(key)) {
      this.keysToTrack = this.keysToTrack.filter((k) => k != key);
    }

    this._saveDataLocaly();
  }

  async reset() {
    await this._removeDataLocaly();
    this.init();
  }

  setOnPhonesLocationsListener(listener) {
    this.onPhonesLocationsListener = listener;
  }

  async allowPhonesToTrackMe(phones) {
    this.allowedKeysToTrackMe = await Promise.all(
      phones.map(async (phone) => await this._getUserKey(phone))
    );

    let ref = database().ref(`permissions/${this.userKey}`);
    const hashes = this.allowedKeysToTrackMe;

    ref.set(hashes);

    this._saveDataLocaly();
  }

  async allowPhoneToTrackMe(phone) {
    const key = await this._getUserKey(phone);

    if (!this.allowedKeysToTrackMe.includes(key)) {
      this.allowedKeysToTrackMe = [...this.allowedKeysToTrackMe, key];
    }

    let ref = database().ref(`permissions/${this.userKey}`);
    const hashes = this.allowedKeysToTrackMe;

    ref.set(hashes);

    this._saveDataLocaly();
  }

  async disallowPhoneToTrackMe(phone) {
    const key = await this._getUserKey(phone);

    if (this.allowedKeysToTrackMe.includes(key)) {
      this.allowedKeysToTrackMe = this.allowedKeysToTrackMe.filter(
        (k) => k != key
      );
      
      let ref = database().ref(`permissions/${this.userKey}`);
      const hashes = this.allowedKeysToTrackMe;

      ref.set(hashes);
    }

    this._saveDataLocaly();
  }

  async _getUserKey(phone) {
    if (phone == "*") {
      return "*";
    }

    const numeric_string = phone.replace(/\D/g,'');

    console.log(phone);
    console.log(numeric_string);

    const key = await sha1(numeric_string.substring(numeric_string.length - 7));
    console.log(key)
    this.keyToPhone[key] = phone;
    return key;
  }
}

Expected Behavior

Contacts locations should update automatically every 10 seconds automatically

Actual Behavior

contacts location does not update very often

Steps to Reproduce

Context

trying to make a find my friends clone app

Debug logs

Logs
PASTE_YOUR_LOGS_HERE

jmcelroy99 avatar Apr 04 '23 14:04 jmcelroy99

Your code doesn't contain any reference to the BackgroundGeolocation plugin.

christocracy avatar Apr 04 '23 14:04 christocracy

whoops,

import BackgroundGeolocation, {
  Location,
  Subscription,
} from "react-native-background-geolocation";

export default class FGLocationTrackingService {
  static instance = null;
  static getInstance() {
    if (this.instance == null) {
      this.instance = new FGLocationTrackingService();
    }
    return this.instance;
  }

  constructor() {
    this.permissionGranted = false;

    this._BGonHeartbeat = this._BGonHeartbeat.bind(this);
  }

  setOnLocationListener(listener) {
    this.onLocationListener = listener;
  }

  init() {
    BackgroundGeolocation.onLocation(this._BGonLocation);

    BackgroundGeolocation.onHeartbeat(this._BGonHeartbeat);

    BackgroundGeolocation.ready({
      desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
      heartbeatInterval: 60,
      preventSuspend: true,
      stopTimeout: 5,

      debug: false,
      logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
      stopOnTerminate: false,
      startOnBoot: true,
      batchSync: false,
      autoSync: true,
    }).then((state) => {
      console.log(
        "- BackgroundGeolocation is configured and ready: ",
        state.enabled
      );
    });
  }

  _BGonHeartbeat(event) {
    console.log("[onHeartbeat]", event.location.coords);
    if (this.onLocationListener) this.onLocationListener(event.location.coords);
  }

  _BGonLocation(event) {
    console.log("[onLocation]", event.coords);
  }

  _onLocationPermissionGranted() {
    this.permissionGranted = true;
  }

  _onLocationPermissionDenied() {
    this.permissionGranted = false;
  }

  requestTrackingPermission() {
    BackgroundGeolocation.requestPermission(
      this._onLocationPermissionGranted,
      this._onLocationPermissionDenied
    );
  }

  startLocationTracking() {
    BackgroundGeolocation.start(
      this._onLocationPermissionGranted,
      this._onLocationPermissionDenied
    );
  }

  stopLocationTracking() {
    BackgroundGeolocation.stop();
  }
}

//   requestTrackingPermission() {
//         Permissions.request(PERMISSIONS.IOS.LOCATION_ALWAYS).then(response => {
//             console.log(response);
//             if (response == "granted") {
//                 this.permissionGranted = true;
//             } else {
//                 this.permissionGranted = false;
//             }
//         });
//     }

//     trackLocation() {
//         GetLocation.getCurrentPosition({
//             enableHighAccuracy: true,
//             timeout: 60000,
//         })
//         .then(location => {
//             console.log(location);
//         })
//         .catch(error => {
//             const { code, message } = error;
//             console.warn(code, message);
//             console.log("WTFFFFF")
//         })
//     }

//     stopLocationTracking() {
//         BackgroundTimer.stopBackgroundTimer();
//     }

//     async sendDataToFirebase(location) {
//         // key is last 8 digits of phone number with sha1 hash
//         const key = Crypto.createHash('sha256')
//             .update(this.phoneNumber.substring(this.phoneNumber.length - 8))
//             .digest('hex')

//         console.log(key);
//         // get sha1 of key

//         //const reference = database().ref('/users/'+);
//     }

//     async startLocationTracking() {
//         console.log("startLocationTracking");
//         if (!this.permissionGranted) {
//             console.log("startLocationTracking: permission not granted");
//             this.requestTrackingPermission();
//             return;
//         }

//         this.sendDataToFirebase(undefined);

//         console.log("startLocationTracking: permission granted");

//         const onLocation = BackgroundGeolocation.onLocation((location) => {
//             console.log('[onLocation]', location.coords);
//           });

//           BackgroundGeolocation.onHeartbeat((event) => {
//             console.log('[onHeartbeat]', event.location.coords);
//             });

//           BackgroundGeolocation.ready({
//             // Geolocation Config
//             desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
//             distanceFilter: 0,
//             heartbeatInterval: 10,
//             disableElasticity: true,
//             preventSuspend: true,

//             // Activity Recognition
//             stopTimeout: 5,
//             // Application config
//             debug: false, // <-- enable this hear sounds for background-geolocation life-cycle.
//             logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
//             stopOnTerminate: true,   // <-- Allow the background-service to continue tracking when user closes the app.
//             startOnBoot: true,        // <-- Auto start tracking when device is powered-up.
//             batchSync: false,       // <-- [Default: false] Set true to sync locations to server in a single HTTP request.
//             autoSync: false,         // <-- [Default: true] Set true to sync each location to server as it arrives.
//           }).then((state) => {
//             console.log("- BackgroundGeolocation is configured and ready: ", state.enabled);
//           });

//           BackgroundGeolocation.start();
//     }

// }

jmcelroy99 avatar Apr 04 '23 14:04 jmcelroy99

Show me evidence of this behaviour with a screenshot of tracking on a map.

christocracy avatar Apr 04 '23 14:04 christocracy

image

jmcelroy99 avatar Apr 04 '23 15:04 jmcelroy99

The light blue dot is my currentLocation and the dark blue dot is my stores location in Db. My dark blue dot won't update in the background or in foreground unless I log out and log back in.

jmcelroy99 avatar Apr 04 '23 15:04 jmcelroy99

Are you testing in the iOS Simulator with "Freeway Drive" and observing the plugin logs in XCode?

christocracy avatar Apr 04 '23 15:04 christocracy

No I am testing on my device with a TestFlight version

jmcelroy99 avatar Apr 04 '23 15:04 jmcelroy99

Have you tried installing the "Demo app" (linked in the README) to compare performance with your own app?

christocracy avatar Apr 04 '23 15:04 christocracy

I suggest you boot your app in XCode Simulator and simulate location with Freeway Drive.

christocracy avatar Apr 04 '23 15:04 christocracy

Okay I'll try this

jmcelroy99 avatar Apr 04 '23 15:04 jmcelroy99

Can you remind me what freeway drive is? Is it a location simulator?

jmcelroy99 avatar Apr 04 '23 15:04 jmcelroy99

Here's what the demo app looks like after one week. It's been tracking like this for the last 8 years, wherever I go in the world.

Screenshot 2023-04-04 at 11 26 50 AM

Simulating with Freeway Drive

Screenshot 2023-04-04 at 11 33 43 AM

christocracy avatar Apr 04 '23 15:04 christocracy

debug: false,

Set it true so you can hear and see what the plug-in is doing

christocracy avatar Apr 04 '23 15:04 christocracy

Should the auto sync prop be set to true or false?

jmcelroy99 avatar Apr 04 '23 15:04 jmcelroy99

Should the auto sync prop be set to true or false?

You aren’t providing the plug-in an url so autoSync is meaningless. How can the plug-in “automatically sync” (ie upload locations) when you’re not providing an url to upload to?

christocracy avatar Apr 04 '23 15:04 christocracy

can we autosync with a firebase backend?

jmcelroy99 avatar Apr 04 '23 16:04 jmcelroy99

If you install the plug-in’s optional Firebase adapter, yes (Uses Firestore only)

https://github.com/transistorsoft/react-native-background-geolocation-firebase

otherwise, you must manage your own Firebase sync with you own JavaScript code.

christocracy avatar Apr 04 '23 16:04 christocracy

thank you for your help, we will work on this today and update you with progress

jmcelroy99 avatar Apr 04 '23 17:04 jmcelroy99

Hey @jmcelroy99 have you had any luck?

MegaPanchamZ avatar May 02 '23 05:05 MegaPanchamZ

This issue is stale because it has been open for 30 days with no activity.

github-actions[bot] avatar May 23 '24 01:05 github-actions[bot]

This issue was closed because it has been inactive for 14 days since being marked as stale.

github-actions[bot] avatar Jun 06 '24 01:06 github-actions[bot]