{"action":"created","issue":{"url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation/issues/514","repository_url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation","labels_url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation/issues/514/labels{/name}","comments_url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation/issues/514/comments","events_url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation/issues/514/events","html_url":"https://github.com/transistorsoft/react-native-background-geolocation/issues/514","id":336390876,"node_id":"MDU6SXNzdWUzMzYzOTA4NzY=","number":514,"title":"Use of removeListeners?","user":{"login":"compojoom","id":693770,"node_id":"MDQ6VXNlcjY5Mzc3MA==","avatar_url":"https://avatars2.githubusercontent.com/u/693770?v=4","gravatar_id":"","url":"https://api.github.com/users/compojoom","html_url":"https://github.com/compojoom","followers_url":"https://api.github.com/users/compojoom/followers","following_url":"https://api.github.com/users/compojoom/following{/other_user}","gists_url":"https://api.github.com/users/compojoom/gists{/gist_id}","starred_url":"https://api.github.com/users/compojoom/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/compojoom/subscriptions","organizations_url":"https://api.github.com/users/compojoom/orgs","repos_url":"https://api.github.com/users/compojoom/repos","events_url":"https://api.github.com/users/compojoom/events{/privacy}","received_events_url":"https://api.github.com/users/compojoom/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-06-27T21:17:55Z","updated_at":"2018-08-15T20:28:28Z","closed_at":null,"author_association":"NONE","body":"Hey Chris,\r\nAs discussed I'm adding this question here. Curious if our use of removeListeners is correct or not.\r\n\r\nIn our app a user is not sharing his location globally, but within companies. In each company the user is able to see the position of other users on a map.\r\n\r\nThe user is either sharing his location indefinitely or for specified amount of time - like 1h, 2h, 1day etc.\r\n\r\nOn the server we have a collection with user positions that look something like this:\r\n```\r\n{\r\nuserA: {position: {lat: 10, lng:10}, companies: {companyA: true, companyB: true}},\r\nuserB: {position: {lat: 11, lng:11}, companies: {companyA: true}}\r\n}\r\n```\r\nOn the map we are just filtering on companyA = true. To send the companies object to the server we have an extras prop specified on the config\r\n```\r\nextras: {\r\n            companies: companies,\r\n            uid: userUid,\r\n            name: acronym(name),\r\n          }\r\n```\r\n\r\nSince a user can opt to share his location with companyA for 30mins and with companyB indefinitely we can't use the build in schedule and we have to monitor the user config and turn off sharing for a company ourselves. \r\n\r\nWhenever the sharingLocation object for the user changes we try to either start listening on the user's position and send the data to the server, or we turn off the plugin. \r\nWe are attaching an http listener and essentially we are using a closure for the sharingLocation variable. This way each time the http method is triggered we can look into the user's config and decide if we want to update it or not. Because of that closure, if we don't remove the listeners before we attach a new listener on http, BackgroundGeolocation will call the old http listerners as well...\r\n\r\nHere is our code\r\n```js\r\n// @flow\r\nimport * as React from 'react'\r\nimport { Alert } from 'react-native'\r\nimport { connect } from 'react-redux'\r\nimport { bindActionCreators } from 'redux'\r\nimport { updatePosition, changeLocationPermission, resetPosition } from '../actions/position'\r\nimport firebase from '../data/firebase'\r\nimport { get, map, difference } from 'lodash'\r\nimport { Actions } from 'react-native-router-flux'\r\nimport moment from 'moment'\r\nimport Config from 'react-native-config'\r\n\r\nimport { acronym } from '../utils/String'\r\n\r\nimport BackgroundGeolocation from 'react-native-background-geolocation'\r\n\r\ntype Props = {\r\n  children: React.Node,\r\n  sharingLocation: { [string]: boolean },\r\n  userUid: string,\r\n  name: string\r\n}\r\n\r\nclass WatchPosition extends React.PureComponent<Props> {\r\n\r\n  url = `${Config.FIREBASE_DATABASE_URL}/testLocations`\r\n  IdToken: null | string = null\r\n\r\n  componentDidMount () {\r\n\r\n    // Listen on the userState\r\n    firebase.auth().onAuthStateChanged((user) => {\r\n      if (user) {\r\n\r\n        // Don't have a token? Generate one, we'll need it for the http requests later\r\n        if (!this.IdToken) {\r\n          user.getIdToken(true).then((token) => {\r\n\r\n            // keep the token in cache\r\n            this.IdToken = token\r\n\r\n            // Listen for position changes\r\n            this.watchPosition()\r\n          })\r\n        }\r\n      } else {\r\n        // If we don't have a user, there is nothing to send to the server\r\n        this.stopBackgroundGeolocation()\r\n      }\r\n    })\r\n\r\n    BackgroundGeolocation.ready({\r\n      reset: true,\r\n      // Geolocation Config\r\n      desiredAccuracy: 0,\r\n      distanceFilter: 20,\r\n      // Activity Recognition\r\n      stopTimeout: 1,\r\n      // Application config\r\n      debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.\r\n      logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,\r\n      stopOnTerminate: false,   // <-- Allow the background-service to continue tracking when user closes the app.\r\n      startOnBoot: true,        // <-- Auto start tracking when device is powered-up.\r\n\r\n    }, (state) => {\r\n\r\n    })\r\n  }\r\n\r\n  componentDidUpdate (prevProps) {\r\n    const currentUser = firebase.auth().currentUser\r\n\r\n    const {sharingLocation} = this.props\r\n\r\n    // Sharing config for locations has changed && we have a logged in user\r\n    if ((prevProps.sharingLocation !== sharingLocation) && currentUser) {\r\n\r\n      // We have a token, then go and watch that position\r\n      if (this.IdToken) {\r\n        this.watchPosition()\r\n      } else {\r\n        // No token yet, get one\r\n        firebase.auth().currentUser.getIdToken(true).then((token) => {\r\n          this.IdToken = token\r\n\r\n          this.watchPosition()\r\n        })\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Watch the user position if necessary. If not, then we make sure to stop the plugin\r\n   */\r\n  watchPosition () {\r\n    const {userUid, sharingLocation, name} = this.props\r\n\r\n    const sharingWithCompanies = map(sharingLocation, (companyId, key) => key)\r\n\r\n    // Only watch the current position if we have a reason to do so (the user has decided to share his location\r\n    // with the companies in sharingLoction)\r\n    if (sharingWithCompanies.length) {\r\n\r\n      // remove any previous listeners, otherwise when the user shares his location multiple times, the\r\n      // listeners will be called multiple times...\r\n      BackgroundGeolocation.removeListeners(() => {\r\n\r\n        // This one is triggered each time when the BackgroundGeolocation plugin sends data to our server\r\n        BackgroundGeolocation.on('http', (response) => {\r\n\r\n          /**\r\n           * A user might decide to share his location with Company A for an hour\r\n           * and with company B indefinitely. That's why we check the sharingLocation\r\n           * and look for restricted sharing. Then we check if the shareTill has expired\r\n           * and if so, we are updating the user Conf object in firestore\r\n           */\r\n          const shouldDelete = map(sharingLocation, (conf, key) => {\r\n            if (conf.type === 'restricted') {\r\n              const expires = moment(conf.shareTill)\r\n\r\n              if (moment().isAfter(expires)) {\r\n                return key\r\n              }\r\n            }\r\n\r\n            return false\r\n          }).filter(value => value !== false)\r\n\r\n          // The user no longer wish to share his location with the companies in the shouldDelete array\r\n          if (shouldDelete.length) {\r\n            shouldDelete.map(key => {\r\n              // Delete the configuration for this company\r\n              firebase.firestore().collection('users').doc(userUid).set({sharingLocation: {[key]: firebase.firestore.FieldValue.delete()}}, {merge: true})\r\n            })\r\n          }\r\n\r\n          // We get a 401, this means that our short live token has expired. Let's generate a new one\r\n          if (401 === response.status) {\r\n            console.log('wtf get new token?=????')\r\n\r\n            firebase.auth().currentUser && firebase.auth().currentUser.getIdToken(true).then((token) => {\r\n              this.resetBackgroundLocationUrl(userUid, token)\r\n            })\r\n          }\r\n          // Success\r\n        }, (response) => {\r\n          console.log('----------------- error http -------------------------', response)\r\n          if (401 === response.status) {\r\n            console.log('wtf error new token???')\r\n            firebase.auth().currentUser && firebase.auth().currentUser.getIdToken(true).then((token) => {\r\n\r\n              this.resetBackgroundLocationUrl(userUid, token)\r\n            })\r\n          }\r\n        })\r\n\r\n        // Get the company ids for the extraData param\r\n        const companies = sharingWithCompanies.map((company, key) => ({[company]: true})).reduce((prev, curr) => {\r\n          return {\r\n            ...prev,\r\n            ...curr\r\n          }\r\n        }, {})\r\n\r\n        // Set the new config\r\n        BackgroundGeolocation.setConfig({\r\n          // HTTP / SQLite config\r\n          url: `${this.url}/${userUid}.json?auth=${this.IdToken}`,\r\n          locationTemplate: '{\"coords\": {\"latitude\": <%= latitude %>, \"longitude\": <%= longitude %>}, \"timestamp\": \"<%= timestamp %>\"}',\r\n          locationAuthorizationRequest: 'Any',\r\n          httpRootProperty: '.',\r\n          method: 'PUT',\r\n          // batchSync: false,       // <-- [Default: false] Set true to sync locations to server in a single HTTP request.\r\n          autoSync: true,         // <-- [Default: true] Set true to sync each location to server as it arrives.\r\n          extras: {\r\n            companies: companies,\r\n            uid: userUid,\r\n            name: acronym(name),\r\n          }\r\n        }, () => {\r\n          // Start the plugin\r\n          BackgroundGeolocation.start(function () {\r\n            console.log('- Start success')\r\n          })\r\n        })\r\n\r\n      }, () => {\r\n        console.log('error removing all listeners')\r\n      })\r\n    }\r\n\r\n    else {\r\n\r\n      // The user isn't sharing his position with any company, so just in case remove any listeners and stop\r\n      // Background Geolocation\r\n      this.stopBackgroundGeolocation()\r\n    }\r\n\r\n  }\r\n\r\n  /**\r\n   * Remove any data stored on the server for this user since he no longer shares his location with any company\r\n   */\r\n  clearFirebaseDatabase () {\r\n    const {userUid} = this.props\r\n\r\n    firebase.database().ref(`testLocations/${userUid}`).remove().catch(error => console.log('couldnt delete location', error))\r\n  }\r\n\r\n  stopBackgroundGeolocation () {\r\n    // You must explicitly stop tracking if currently enabled\r\n    BackgroundGeolocation.stop(() => {\r\n      this.clearFirebaseDatabase()\r\n    })\r\n\r\n  }\r\n\r\n  resetBackgroundLocationUrl (userUid, token) {\r\n    BackgroundGeolocation.setConfig({\r\n      url: `${this.url}/${userUid}.json?auth=${token}`,\r\n    })\r\n  }\r\n\r\n  render () {\r\n    return this.props.children\r\n  }\r\n}\r\n\r\nfunction mapDispatchToProps (dispatch) {\r\n  return {\r\n    actions: bindActionCreators(\r\n      Object.assign(\r\n        {},\r\n        {\r\n          updatePosition,\r\n          changeLocationPermission,\r\n          resetPosition\r\n        }\r\n      ),\r\n      dispatch\r\n    )\r\n  }\r\n}\r\n\r\nconst selector = (state, ownProps) => {\r\n  return {\r\n    userUid: get(state, 'firebase.profile.uid'),\r\n    name: get(state, 'firebase.profile.displayName'),\r\n    sharingLocation: get(state, 'firebase.profile.sharingLocation'),\r\n    avatar: get(state, 'firebase.profile.avatar.original.downloadURL')\r\n  }\r\n}\r\n\r\nexport default connect(selector, mapDispatchToProps)(WatchPosition)\r\n```\r\nIs this a valid use of removeListeners, or do you still think that we are doing this wrong and we should just call stop?"},"comment":{"url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation/issues/comments/413325245","html_url":"https://github.com/transistorsoft/react-native-background-geolocation/issues/514#issuecomment-413325245","issue_url":"https://api.github.com/repos/transistorsoft/react-native-background-geolocation/issues/514","id":413325245,"node_id":"MDEyOklzc3VlQ29tbWVudDQxMzMyNTI0NQ==","user":{"login":"christocracy","id":14999,"node_id":"MDQ6VXNlcjE0OTk5","avatar_url":"https://avatars1.githubusercontent.com/u/14999?v=4","gravatar_id":"","url":"https://api.github.com/users/christocracy","html_url":"https://github.com/christocracy","followers_url":"https://api.github.com/users/christocracy/followers","following_url":"https://api.github.com/users/christocracy/following{/other_user}","gists_url":"https://api.github.com/users/christocracy/gists{/gist_id}","starred_url":"https://api.github.com/users/christocracy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/christocracy/subscriptions","organizations_url":"https://api.github.com/users/christocracy/orgs","repos_url":"https://api.github.com/users/christocracy/repos","events_url":"https://api.github.com/users/christocracy/events{/privacy}","received_events_url":"https://api.github.com/users/christocracy/received_events","type":"User","site_admin":false},"created_at":"2018-08-15T20:28:28Z","updated_at":"2018-08-15T20:28:28Z","author_association":"MEMBER","body":"I've lost touch with this issue since my summer vacation.  Can this be closed?"}}
{"actor":{"display_login":"christocracy"}}