transistorsoft / react-native-background-geolocation

Sophisticated, battery-conscious background-geolocation with motion-detection
http://shop.transistorsoft.com/pages/react-native-background-geolocation
MIT License
2.62k stars 428 forks source link

Enabled returning false despite location icon appearing in status bar, permissions not being requested #1051

Closed mgoldfi1 closed 4 years ago

mgoldfi1 commented 4 years ago

Plugin version: 3.6.2 Platform: iOS, but making app for both OS version: target is iOS 9.0 Device manufacturer / model: iPhone 8 running iOS 13.1 React Native version (react-native -v): 0.61.5 Plugin config

import React, {useEffect} from 'react';
import {View, TouchableOpacity, Alert} from 'react-native';
import {connect} from 'react-redux';
import {sendAlert} from '../../src/redux/actions/index';
import Geolocation from '@react-native-community/geolocation';
import BackgroundGeolocation from 'react-native-background-geolocation';

import Container from '../Components/Container';
import Box from '../Components/Box';
import Icon from 'react-native-vector-icons/FontAwesome';
import styled from 'styled-components/native';
import Text from '../Components/Text';
const CircleButton = styled.TouchableOpacity`
  height: 125;
  width: 125;
  border-radius: 62.5;
  border: 0.5px solid grey;
  margin-bottom: 8;
  background-color: white;
  justify-content: center;
  align-items: center;
  shadowOffset: {width: 2, height: 2};
  shadowColor: grey;
  shadowOpacity: 0.2;
`;
const Image = styled.Image`
  height: 50;
  width: 50;
`;

const SendAlert = ({navigation, user, sendAlert, token}) => {
  useEffect(() => {
    BackgroundGeolocation.onLocation(onLocation, onError);

    // This handler fires when movement states changes (stationary->moving; moving->stationary)
    BackgroundGeolocation.onMotionChange(onMotionChange);

    // This event fires when a change in motion activity is detected

    // This event fires when the user toggles location-services authorization
    BackgroundGeolocation.onProviderChange(onProviderChange);

    ////
    // 2.  Execute #ready method (required)
    //
    BackgroundGeolocation.ready(
      {
        // Geolocation Config
        desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
        distanceFilter: 10,
        // Activity Recognition
        stopTimeout: 1,
        // Application config
        debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.
        logLevel: BackgroundGeolocation.LOG_LEVEL_VERBOSE,
        stopOnTerminate: false, // <-- Allow the background-service to continue tracking when user closes the app.
        startOnBoot: true, // <-- Auto start tracking when device is powered-up.
      },
      state => {
        console.log(
          '- BackgroundGeolocation is configured and ready: ',
          state.enabled,
          state,
        );

        if (!state.enabled) {
          ////
          // 3. Start tracking!
          //
          BackgroundGeolocation.start(function() {
            console.log('- Start success');
          });
        }
      },
    );

    return () => {
      console.log('unmounted');
      BackgroundGeolocation.removeListeners();
      BackgroundGeolocation.stop();
    };
  }, []);

  const onLocation = location => {
    console.log('[location] -', location);
  };
  const onError = error => {
    console.warn('[location] ERROR -', error);
  };

  const onProviderChange = provider => {
    console.log('[providerchange] -', provider.enabled, provider.status);
  };
  const onMotionChange = event => {
    console.log('[motionchange] -', event.isMoving, event.location);
  };

  const makeAlert = (lat, lng, type) => {
    const data = {
      Phone: '*******',
      type,
      lat: lat.toString(),
      lng: lng.toString(),
    };
    sendAlert(data, token);
  };
  const findCoordinates = async type => {
    Geolocation.getCurrentPosition(
      location => {
        console.log(location);
        const geo = {
          lat: location.coords.latitude,
          lng: location.coords.longitude,
        };
        Alert.alert(
          `An alert (${type} Emergency) will be sent`,
          `Are you sure? lat:  ${geo.lat}, lng:  ${geo.lng}`,
          [
            {
              text: 'Send Alert',
              onPress: () => makeAlert(geo.lat, geo.lng, type),
            },
            {text: 'Cancel'},
          ],
        );
      },
      error => Alert.alert(error.message),
      {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000},
    );
  };

  const handlePress = async type => {
    await findCoordinates(type);
  };
  return (
    <Container>
      <Box
        style={{
          padding: '5%',
          paddingTop: 25,
          flexDirection: 'column',
          flexWrap: 'wrap',
        }}>
        <Box>
          <CircleButton onPress={() => handlePress('Active Shooter')} />
          <Text fontStyle={'light'} fontSize={'medium'} color={'black'}>
            Active Shooter
          </Text>
        </Box>
        <Box style={{flexDirection: 'row'}}>
          <Box>
            <CircleButton onPress={() => handlePress('Health')}>
              <Image source={require('../Assets/images/health.png')} />
            </CircleButton>
            <Text fontStyle={'light'} fontSize={'medium'} color={'black'}>
              Medical
            </Text>
          </Box>
          <Box>
            <CircleButton onPress={() => handlePress('Fire')}>
              <Image source={require('../Assets/images/fire.png')} />
            </CircleButton>
            <Text fontStyle={'light'} fontSize={'medium'} color={'black'}>
              Fire
            </Text>
          </Box>
        </Box>
        <Box>
          <CircleButton onPress={() => handlePress('Other')}>
            <Image source={require('../Assets/images/other.png')} />
          </CircleButton>
          <Text fontStyle={'light'} fontSize={'medium'} color={'black'}>
            Other
          </Text>
        </Box>
      </Box>
      <TouchableOpacity
        onPress={() => navigation.navigate('Home')}
        style={{
          position: 'absolute',
          top: 25,
          left: 25,
          height: 36,
          width: 36,
          borderRadius: 18,
          backgroundColor: 'black',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <View>
          <Icon name="arrow-left" size={24} color={'white'} />
        </View>
      </TouchableOpacity>
    </Container>
  );
};

export default connect(
  state => {
    const user = state.user.user;
    const token = state.auth.token;
    return {user, token};
  },
  {sendAlert},
)(SendAlert);

Expected Behavior

The idea is that when an app user presses a button and hits "allow", i want the location to be tracked actively until they hit a stop button.

Actual Behavior

Ready function is returning enabled: false, despite me still hearing the sounds and getting the onActivityChangelistener hit. Additionally, the permissions are not being successfully requested, while the other package im using "Geolocation" is setting the permission to while in use and RNBG is not requesting for always in use. Even if i change it manually to always, enabled still returns false.

Steps to Reproduce

  1. Open screen
  2. Component mounts and listeners should be attached
  3. ready function returns false even with permissions manually set to always allow (even though its not requesting that on its own)
  4. component unmounts, should stop geolocating and stop tracking but only stops if i call .stop()

Context

I am working on an application for my job where a user will be able to click a button that will create an event that captures their lat and lng at that moment. After submitting a post request, I want there to be another alert that requests that they allow background tracking until they manually disable it. For this I will basically need a start/stop button which i have seen is possible from the demo apps and video. I want them to be tracked even if the phone screen is closed or the app is minimized, but not if the app is terminated or the phone is shut off. I saw settings for this but realized I should worry about that once I get the feature working in its basic form.

Debug logs

Logs ``` From console logs: - BackgroundGeolocation is configured and ready: false {showsBackgroundLocationIndicator: false, elasticityMultiplier: 1, activityType: 1, disableStopDetection: false, didLaunchInBackground: false, …} activityRecognitionInterval: 10000 activityType: 1 authorization: {} autoSync: true autoSyncThreshold: 0 batchSync: false debug: true desiredAccuracy: -1 desiredOdometerAccuracy: 100 didLaunchInBackground: false disableAutoSyncOnCellular: false disableElasticity: false disableLocationAuthorizationAlert: false disableMotionActivityUpdates: false disableStopDetection: false distanceFilter: 10 elasticityMultiplier: 1 enableTimestampMeta: false enabled: false encrypt: false extras: {} geofenceInitialTriggerEntry: true geofenceProximityRadius: 2000 geofenceTemplate: "" headers: {} heartbeatInterval: 60 httpRootProperty: "location" httpTimeout: 60000 iOSHasWarnedLocationServicesOff: false isFirstBoot: false isMoving: false lastLocationAuthorizationStatus: 3 locationAuthorizationAlert: {settingsButton: "Settings", cancelButton: "Cancel", titleWhenNotEnabled: "Background location is not enabled", instructions: "To use background location, you must enable '{loca…zationRequest}' in the Location Services settings", titleWhenOff: "Location services are off"} locationAuthorizationRequest: "Always" locationTemplate: "" locationTimeout: 60 locationsOrderDirection: "ASC" logLevel: 5 logMaxDays: 3 maxBatchSize: -1 maxDaysToPersist: 1 maxRecordsToPersist: -1 method: "POST" minimumActivityRecognitionConfidence: 70 odometer: 0 params: {} pausesLocationUpdatesAutomatically: false persistMode: 2 preventSuspend: false schedule: [] schedulerEnabled: false showsBackgroundLocationIndicator: false startOnBoot: true stationaryRadius: 25 stopAfterElapsedMinutes: -1 stopDetectionDelay: 0 stopOnStationary: false stopOnTerminate: false stopTimeout: 1 trackingMode: 1 url: "" useSignificantChangesOnly: false } [location] - {coords: {…}, extras: {…}, sample: true, is_moving: false, odometer: 0, …} SendAlert.js:47 - Start success SendAlert.js:68 [motionchange] - false {extras: {…}, coords: {…}, is_moving: false, event: "motionchange", odometer: 0, …} SendAlert.js:64 [location] - {coords: {…}, extras: {…}, is_moving: false, event: "motionchange", odometer: 0, …} ``` [logs.docx](https://github.com/transistorsoft/react-native-background-geolocation/files/4611898/logs.docx)
christocracy commented 4 years ago

Do you know what this does?

if (!state.enabled) {
          ////
          // 3. Start tracking!
          //
          BackgroundGeolocation.start(function() {
            console.log('- Start success');
          });
        }
christocracy commented 4 years ago

Btw, that block is only there for example purposes. It's expected you'll execute start() by some user action or some other event.

In practice you shouldn't automatically start() the plugin as soon as #ready is executed.

mgoldfi1 commented 4 years ago

That should start the plugin correct? I tried with both if (!state.enabled) and (state.enabled),but obviously in my case its only getting hit with that version because im returning false.

mgoldfi1 commented 4 years ago

I understand that, I was going to add the start command to a button press handler on an alert, but I was more concerned with the permissions not being properly requested no matter what I do. Could this be a plist issue or do you think that what you just said is possibly causing a problem outside of poor practice.

christocracy commented 4 years ago

I have no idea what you're trying to do.

  1. Always call #ready as soon as your app is launched, no matter what.
  2. When you want to track the device, call start(). When you want to stop tracking the device, call stop().
christocracy commented 4 years ago

Could this be a plist issue

See the iOS Setup Docs and follow them exactly.

mgoldfi1 commented 4 years ago

So i should move the .ready() into the app level component App.js on mount, and only call start on the screen I want it to start on? I should add that in my attempts to fix this I have already tried this, and moved all functions to my App.js file but the same issue occurred.

mgoldfi1 commented 4 years ago

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>com.transistorsoft.fetch</string>
    </array>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>AlertMobile</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
        <key>NSExceptionDomains</key>
        <dict>
            <key>localhost</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
            </dict>
        </dict>
    </dict>
    <key>NSCameraUsageDescription</key>
    <string>AlertMobile requires access to your phone’s camera.</string>
    <key>NSFaceIDUsageDescription</key>
    <string>Enabling Face ID allows you quick and secure access to your account.</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>CHANGEME: Location Always And When In Use Usage Description</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>CHANGEME: Location Always Usage Description</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Location Required When App Is In Use</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>AlertMobile requires access to your phone’s Microphone.</string>
    <key>NSMotionUsageDescription</key>
    <string>CHANGEME: Motion updates increase battery efficiency by intelligently toggling location-services when device is detected to be moving</string>
    <key>UIAppFonts</key>
    <array>
        <string>Ionicons.ttf</string>
        <string>Montserrat-Black.ttf</string>
        <string>Montserrat-BlackItalic.ttf</string>
        <string>Montserrat-Bold.ttf</string>
        <string>Montserrat-BoldItalic.ttf</string>
        <string>Montserrat-ExtraBold.ttf</string>
        <string>Montserrat-ExtraBoldItalic.ttf</string>
        <string>Montserrat-ExtraLight.ttf</string>
        <string>Montserrat-ExtraLightItalic.ttf</string>
        <string>Montserrat-Italic.ttf</string>
        <string>Montserrat-Light.ttf</string>
        <string>Montserrat-LightItalic.ttf</string>
        <string>Montserrat-Medium.ttf</string>
        <string>Montserrat-MediumItalic.ttf</string>
        <string>Montserrat-Regular.ttf</string>
        <string>Montserrat-SemiBold.ttf</string>
        <string>Montserrat-SemiBoldItalic.ttf</string>
        <string>Montserrat-Thin.ttf</string>
        <string>Montserrat-ThinItalic.ttf</string>
        <string>AntDesign.ttf</string>
        <string>Entypo.ttf</string>
        <string>EvilIcons.ttf</string>
        <string>Feather.ttf</string>
        <string>FontAwesome.ttf</string>
        <string>FontAwesome5_Brands.ttf</string>
        <string>FontAwesome5_Regular.ttf</string>
        <string>FontAwesome5_Solid.ttf</string>
        <string>Fontisto.ttf</string>
        <string>Foundation.ttf</string>
        <string>MaterialCommunityIcons.ttf</string>
        <string>MaterialIcons.ttf</string>
        <string>Octicons.ttf</string>
        <string>SimpleLineIcons.ttf</string>
        <string>Zocial.ttf</string>
    </array>
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
        <string>fetch</string>
        <string>location</string>
        <string>processing</string>
        <string>remote-notification</string>
    </array>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
</dict>
</plist>

sorry about that i deleted the comment and was in the process of fixing it

christocracy commented 4 years ago

with the permissions not being properly requested

I don't know what this means.

mgoldfi1 commented 4 years ago

I mean I am not getting a popup to request "Always allow location" ever, even after a clean rebuild. I occasionally get the motion tracking request but its not consistent either.

christocracy commented 4 years ago

I suggest you go build yourself a Simple Hello World and first learn to use the plugin in isolation before attempting to integrate it into your own app.

christocracy commented 4 years ago

And perhaps you're not aware of the changes to Location Authorization that iOS 13 introduced?

mgoldfi1 commented 4 years ago

I have used this package a year ago on a professional project without running into any of these issues. It was deployed without a problem. I don't see the utility of me building a standalone version of this while I'm making a complex project that requires several other packages and pods.

I also have manually set the setting to "Always allow" to rule out that issue. I am still getting an enabled false from the .ready method, with the always allow option set up. Even though its returning false, the onActivityChange listener still gets hit every half a second even while standing still which is also confusing to me. Shouldn't the listeners not be firing if it was not ready?

christocracy commented 4 years ago
mgoldfi1 commented 4 years ago

Ok so, interestingly enough I made a few developments here (logs to follow). I opened the iPhone 11 simulator, moved the listeners and .ready() to the App.js componentDidMount. Then I moved the start and stop to the buttons on another screen where I want them to be. I started the plugin, got the permission request for location (as expected, I hit allow while using). I then simulated a freeway drive with xcode and i was receiving the location listener repeatedly which is the ideal behavior. As per your video on iOS 13, the background fetching didnt work until i minimized the app and hit always allow but this was a bit weird because it didnt pop up the first time i minimized it. I am now wondering why this worked on the simulator but not my physical testing device. Is it possible that I have listeners or some app settings being cached somewhere that are causing it not to work? If so do you have a recommended way of clearing those on a physical device because deleting the app and rebuilding it did not cut it.

As promised, here are my logs for the iPhone 11 simulator. xcodelogs.txt

Thank you for your help so far, this has been an interesting development that it works as intended on the simulator. I'm interested in hearing your take on this.

christocracy commented 4 years ago

but not my physical testing device

I have no idea but it's most likely something on your end. I test the plugin every day and it never fails, including today's grocery-trip.

mgoldfi1 commented 4 years ago

I am not suggesting your plugin does not work. I'm just having difficulties using it as to some unknown reason on my end and was looking for help. I am almost positive it has something to do with me reloading the app a bunch of times with hotreloading and something is getting cached somewhere.

christocracy commented 4 years ago

If I were you, I'd be looking at this and disabling it to see what happens.

return () => {
      console.log('unmounted');
      BackgroundGeolocation.removeListeners();
      BackgroundGeolocation.stop();
    };
christocracy commented 4 years ago
stopOnTerminate: false

If you're hoping for the plugin to resume tracking after app-terminate, you'd better be placing the call to #ready in a place that gets called no matter what, each time the app boots, or it won't resume tracking after terminate.

mgoldfi1 commented 4 years ago

I moved that to componentDidMount of App.js which will be called again after a terminate which makes more sense than having it on the other screen. I wanted to make sure that this process is very controlled because the main focus of the app is not permanent tracking but instead only based on triggered events, push notifications, or websocket events.

christocracy commented 4 years ago

ready doesn’t imply initiating tracking or requesting permission. #start / #stop does.

ready crucially let’s the plugin know that your app UI is launched and available for receiving events.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. You may also mark this issue as a "discussion" and I will leave this open.

stale[bot] commented 4 years ago

Closing this issue after a prolonged period of inactivity. Fell free to reopen this issue, if this still affecting you.

usmanhashmi2123 commented 1 year ago

Facing the same issue @mgoldfi1