Cannot get current position without GPS on react-native with navigator.geolocation - react-native

Brief summary after discussion and answers:
using EXPO sdk you cannot get the device location without grant FINE_LOCATION in android. FINE_LOCATION is the only method to get location, so, you cannot get the hardware.network.location. That means: with android > 6 you cannot get the current location using WIFI/mobile networks, you must enable Location.
Expo github discussion: https://github.com/expo/expo/issues/1339
The initial problem:
im working on a react-native application, using expo and react-native-maps, and i need to get the latitude and longitud of the user current position.
Im using navigator.geolocation API for that
with the GPS turned on i have no problems, but i need to get the current position without GPS, based on the network provider.
The problem is that when the application runs with expo on androiod > 6 i get this error:
21:50:53: Finished building JavaScript bundle in 449ms 21:50:55:
Location services are disabled
- node_modules\react-native\Libraries\BatchedBridge\NativeModules.js:80:57
in
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:347:19
in __invokeCallback
- ... 4 more stack frames from framework internals
In IOs and android <=5 it works great.
Here is the code of the component:
class MyComponent extends Component {
componentWillMount = () => {
this.getCurrentLocation();
}
getCurrentLocation = () =>
navigator.geolocation.getCurrentPosition(
(position) => {
let currentUserPosition = position.coords;
alert(JSON.stringify(currentUserPosition));
},
(error) => {
console.log(error);
},
{
enableHighAccuracy: false,
timeout: 20000,
maximumAge: 0,
distanceFilter: 10
}
);
}
And this are my package.json depencendies:
"dependencies": {
"expo": "23.0.4",
"native-base": "^2.3.5",
"react": "16.0.0",
"react-native": "0.50.4",
"react-native-extend-indicator": "^0.1.2",
"react-native-maps": "^0.19.0",
"react-native-maps-directions": "^1.3.0",
"react-native-router-flux": "4.0.0-beta.21",
"react-navigation": "1.0.0-beta.21",
"react-navigation-redux-debouncer": "^0.0.2",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
}
I expect that navigator.geolocation get the location based on the network provider in that situation (without gps), the specification saids that..
i also tried with the Geolocation API of expo (tried this example: https://snack.expo.io/#schazers/expo-map-and-location-example) , and with the GPS turned OFF i cant get my location..
so.. is there a way to achieve what i want? i am missing something?
EDIT (EXPO CONTRIBUTOR ANSWER):
I have posted the same at expo github (https://github.com/expo/expo/issues/1339), according to them it is imposible to get the current position using navigator.geolocation without any level of Location enabled in a android device.. so .. the only thing that could happen is that android versions older than 5 has location enabled by default and you can turn on just the GPS, and the versions 6 and forward you must specify the location level ..
any ideas?
EDIT2 (IMPORTANT !!):
I have confirmed this:
this is security settings of a Android 6 device, by default it uses GPS, i think that android 5 or lower doesnt, so thats the thing.. when i use the 2nd option it gets me the location !
the fact is that forcing a user to enable Location is like "hey, use your GPS!", and there are a lot of applications that gives you a aproximated position without turning on Location (like Uber for example), so the question is, is there a way that ONLY using wifi get the location with this api?

The problem you are dealing with here is with EXPO, because the location request API has changed after Android 5(API 21).
Before API 21, you needed to add to ACCESS_FINE_LOCATION (Both Wifi/Network and GPS providers) and ACCESS_COARSE_LOCATION (only GPS permission) location permissions. However, since Android 5, the apis changes to android.hardware.location.network and android.hardware.location.gps.
When your target users include both Android 5 +/-. you need to add both permission types to your android manifest, for example:
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- for Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:name="android.hardware.location.network" />
</manifest>
However, it seems that the people in expo, only included, the new permission in the android manifest. So what you need to do is to change android manifest. However, expo only gives access to pure JS part of the program and not to native codes. As a result, you need to detach from EXPO to add native modules or changes.
In expo documentation, the detachment process is explained in this link, the procedure is simple:
install expo package globally using npm:
npm install -g exp
Inside your project directory, run detachment code, it will build android and ios folders in your project directory:
exp detach
Then you can make the changes you need in android manifest file.

Common bug in Android. Try without using the third parameter:
getCurrentLocation = () =>
navigator.geolocation.getCurrentPosition(
(position) => {
let currentUserPosition = position.coords;
alert(JSON.stringify(currentUserPosition));
},
(error) => {
console.log(error);
}
);
So, I just digged a little into the code and basically all the LocationModule does is send the request directly to Android.
It just calls this method:
https://developer.android.com/reference/android/location/LocationManager.html#getLastKnownLocation(java.lang.String)
Now, if you read that, the location says FINE_LOCATION or COARSE_LOCATION.
Usually one just add the fine location permissions, but I am thinking that maybe to access the Wifi location you need to add the coarse permissions into the app. Do you have both?
https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_COARSE_LOCATION
If not, I would add that permissions into the app and try again.

I was having the same issue and tried a lot of libraries to get the user location. navigator.geolocation API did not gave me location every time on android instead it just give mostly timeout error only. So then I tried react native background geolocation that start using the gps continuously then I find react native geolocation service and the author clearly mentioned that:
Since this library was meant to be a drop-in replacement for the RN's
Geolocation API, the usage is pretty straight forward, with some extra
error cases to handle.
thats what I needed always and I think you should also try this.
Have a look on this AndroidManifest file, the author is using all type of location.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.agontuk.RNFusedLocation">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
for the test example you'll simply follow the same instruction that RN Geolocation API suggesting. Here is the simple example using react native geolocation service:
import Geolocation from 'react-native-geolocation-service';
componentDidMount() {
// Instead of navigator.geolocation, just use Geolocation.
if (hasLocationPermission) {
Geolocation.getCurrentPosition(
(position) => {
console.log(position);
},
(error) => {
// See error code charts below.
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
}
}
So in the third parameter of the getCurrentPosition use enableHighAccuracy false to use network to get user location and true to get more accurate location using gps. See this option documentation.
Author of this repo also provided the example project you can try it with enableHighAccuracy false and turn location off for the first time but when you run the app it will ask for the permission of the location it will show app want to access wifi or cell network provided location.
As you are using expo you cannot use react native geolocation service for that you need to follow Mojtaba answer for detachment and get the android and iOS project so that you can configure both projects. Let me know if you need more information.

In my case, I solved my issue by adding the following in AndroidManifest.xml.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

First of all give permissions in your app
For Ios: You need to include the NSLocationWhenInUseUsageDescription key in Info.plist
For android:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
From here we get lat and longitude
CODE ==>
navigator.geolocation.getCurrentPosition(
(position) => {
const initialPosition = JSON.stringify(position);
// console.log(initialPosition);
this.setState({ initialPosition });
},
//(error) => alert(error.message),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
this.watchID = navigator.geolocation.watchPosition((position) => {
const lastPosition = JSON.stringify(position);
//this.setState({ longitude: position.coords.longitude, latitude: position.coords.latitude });
//this._getWheaterData();
AsyncStorage.setItem('latitude',position.coords.latitude.toString())
AsyncStorage.setItem('longitude',position.coords.longitude.toString())
},
// (error) => alert(error.message),
// console.log(error),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 });

Related

Expo - How to make custom permission message

I need to change the camera message when I try to reach user's camera.
It returns default "Allow xx to use your camera" message and I want to change It with my own permission message.
Where to change It and how?
Here's my camera code below.
useEffect(() => {
(async function () {
const { status } = await Camera.requestPermissionsAsync()
setHasPermission(status === 'granted')
})()
}, [])
From the Expo documentation:
To request permissions on iOS, you have to describe why the permissions are requested and install the library that can request this permission. In the managed workflow, you can do that by customizing the ios.infoPlist property in your app.json file. When using the bare workflow, you have to edit the info.plist file directly.
So, in app.json:
"infoPlist": {
"NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets."
}
In a non-managed workflow, you can edit NSCameraUsageDescription in your Info.plist for the app in Xcode.

How to force users to update the app using react native

I have updated my app on app and play store and I want to force my app users to update the new version of app in App store and playstore.
You can check for the App Store / Play Store version of your app by using this library
react-native-appstore-version-checker.
In expo app you can get the current bundle version using Constants.nativeAppVersion. docs.
Now in your root react native component, you can add an event listener to detect app state change. Every time the app transitions from background to foreground, you can run your logic to determine the current version and the latest version and prompt the user to update the app.
import { AppState } from 'react-native';
class Root extends Component {
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextState) => {
if (nextState === 'active') {
/**
Add code to check for the remote app version.
Compare it with the local version. If they differ, i.e.,
(remote version) !== (local version), then you can show a screen,
with some UI asking for the user to update. (You can probably show
a button, which on press takes the user directly to the store)
*/
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
}
import VersionCheck from 'react-native-version-check';
i have used version check lib for this purpose and approach i used is below. if version is lower i'm opening a modal on which an update button appears, and that button redirects to app store/google play
componentDidMount() {
this.checkAppUpdate();
}
checkAppUpdate() {
VersionCheck.needUpdate().then(res => {
if (res.isNeeded) {
setTimeout(() => {
this.setState({openModal: true});
});
}
});
}
updateApp = () => {
VersionCheck.getStoreUrl({
appID: 'com.showassist.showassist',
appName,
})
.then(url => {
Linking.canOpenURL(url)
.then(supported => {
if (!supported) {
} else {
return Linking.openURL(url);
}
})
.catch(err => console.error('An error occurred', err));
})
.catch(err => {
console.log(`error is: ${err}`);
});
};
For future readers.
If you are using Expo managed workflow, install this package react-native-version-check-expo using yarn add react-native-version-check-expo or npm install react-native-version-check-expo.
Consult the package documentation on Github for usage guidelines.
I'm using react-native-version-check-expo library to achieve this. Working fine for me.
if you are looking for an easy to integrate built in solution. You can use App Upgrade https://appupgrade.dev/ service to force update your mobile apps.
Create new version entry for your app version that you want to update in the app upgrade service and select whether you want to force it or just want to let users know that new version is available.
Integrate your app with App Upgrade using available SDK. Official SDK are available for React Native, Flutter, Expo, Android and iOS(Swift).
The SDK will take care of the rest.
Whenever you want to force upgrade a version just create a version entry in app upgrade dashboard.
You can also integrate using API. Just call the appupgrade api from your app with the required details such as your app version, platform, environment and app name.
The API will return you the details.. that this app needs to be updated or not.
Based on the response you can show popup in your app.You can call this API when app starts or periodically to check for the update. You can even provide a custom message.
API response:
See the response has force update true. So handle in the app by showing popup.
You can find the complete user documentation here. https://appupgrade.dev/docs
Thanks.

React native app : Not authorized to use location services

I'm currently working on a project that uses the geolocation service of my phone.
I currently have a prolem with this service, it says that geolocation is not authorized.
I've tried to look on the internet and a few people had the same problem but I didn't manage to fix it...
componentDidMount() {
const { coordinate } = this.state;
this.requestCameraPermission();
this.watchID = navigator.geolocation.watchPosition(
position => {
const { routeCoordinates, distanceTravelled } = this.state;
const { latitude, longitude } = position.coords;
const newCoordinate = {
latitude,
longitude
};
console.log({ newCoordinate });
coordinate.timing(newCoordinate).start();
this.setState({
latitude,
longitude,
routeCoordinates: routeCoordinates.concat([newCoordinate]),
distanceTravelled:
distanceTravelled + this.calcDistance(newCoordinate),
prevLatLng: newCoordinate
});
},
error => console.log(error),
{
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 1000,
distanceFilter: 10
}
);
}
Instead of appearing on my current spot, i'm in San Fransisco (which is the default location on maps).
The function navigator.geolocation.watchPosition gets the error :
"code": "E_LOCATION_UNAUTHORIZED",
"message": "Not authorized to use location services",
"watchId": 1,
My phone is a Samsung S9 and the location service is enabled... So I'm really curious about the problem I have right now.
Thank you for your answer, I managed to fix my problem, apparently expo wasn't allowed to use my location for some reason, so I just forced it with :
Location.requestPermissionsAsync();
import * as Permissions from 'expo-permissions';
await Permissions.askAsync(Permissions.LOCATION);
Alternative method if requestPermissionsAsync(); is unable to work.
If you are using Expo client on your mobile then please make sure you have enabled the location permission.
If you already given the permission, then check your AndroidManifest.xml for
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> for these locations.
I've had the case when iOS worked fine without asking explicitly in the .ts code for any permissions. However, Android showed that warning Not authorized to use location services.
The Permissions have slightly different syntax now, here is an example with Location.
if (Platform.OS !== "web") {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
Alert.alert(
"Insufficient permissions!",
"Sorry, we need location permissions to make this work!",
[{ text: "Okay" }]
);
return;
}
}
(in general) Don't mess up anything in the AndroidManifest.xml, like <uses-permission android:name=..., because whatever Expo needs will generate automatically there. That's the case for Location, citing official docs:
"Some Expo and React Native modules include permissions by default. If you use expo-location, for example, both the ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION are implied and added to your app's permissions automatically."
If you are using react native you may need to add following in your AndroidManifest.xml file,
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

React Native geolocation heading is always -1 on iPhone 7

I am faced an issue that I can't solve for a weeks.
Task: I heed to watch for a device position and its direction.
Solution: I use method as described in docs
this.watcher = navigator.geolocation.watchPosition((position) => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
const heading = position.coords.heading;
this.setState({
latitude: latitude,
longitude: longitude,
heading: heading
});
},
(error) => {
console.log(`error: ${error}`);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
});
Eevrything works fine on simulator. But on a real iOS device heading is always -1.
My project use RN 0.54. However I just created fresh project with RN 0.56 via react-native init and behavior is the same.
The thing is that in react-native-maps package user direction renders in a perfect way, so I assume that there may be some issue with React Native
Any help?
Since React Native geolocation is an extension over web geolocation (see docs),
it gets orientation(heading) as a difference between two last positions. Which is not working at low speed or when user stays (returns -1).
Which is strange, because I expected RN to get device heading from built in "compass".
Solution for ejected apps: use thirds party library react-native-heading to get device heaing even when user stays.
Solution for Expo: there should be no problem like this because Expo use built in compass instead of web geolocation.
Using Location.getHeadingAsync() I was able to get a heading direction to not be -1.
Check out this link for the expo method I am using:
https://docs.expo.io/versions/latest/sdk/location#expolocationgetheadingasync-215

Worklight 6.1 Location Services Sample works in simulator, but not actual phone

I can't get the Worklight "Location Services" "SmallSample" app to work on my Droid 4 phone. I am using the "smallSample" sample project provided by IBM Worklight.
The "SmallSample" app works great in the Mobile Browser Simulator, but when I install it on my physical phone.
When I press the button on the app to retrieve my GPS coordinates, the Android GPS icon appears for about 2 seconds in my notification bar, then disappears. The GPS coordinates are never displayed, and there are no errors.
Details:
I'm using Worklight Developer Edition 6.1.0.01
My phone is the Droid 4, Android 4.1.2 (API 16)
I imported the app to the Studio, then exported a signed APK using a new Keystore, and installed it on my phone.
I'm outside when using the app to ensure I can get GPS signal.
Other GPS apps work on my phone (Google maps).
I verified connectivity to my Worklight Server by opening the Worklight console with my mobile browser.
In the Worklight settings of the app, I verified the app is using the correct IP address, port, and context root to my Worklight Server.
I verified all the default permissions are there including:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
UPDATE:
Without changing anything else, I was able to retrieve GPS coordinates on my device a single point in time by using the code from this question: IBM Worklight - How to implement GPS functionality?
navigator.geolocation.getCurrentPosition(onSuccess, onError);
function onSuccess(position) {
alert(JSON.stringify(position));
}
function onError(error) {
alert(JSON.stringify(error));
}
The code above works perfectly on my phone and GPS coordinates are returned. However, the code from SmallSample (referenced in the link above) still doesn't work. One difference is the GPS coordinates are continually updated in Small Sample. I pasted the main part of the code below.
WL.Device.Geo.acquirePosition(
function(pos) {
// when we receive the position, we display it and start on-going acquisition
displayPosition(pos);
var triggers = {
Geo: {
posChange: { // display all movement
type: "PositionChange",
callback: function(deviceContext) {
displayPosition(deviceContext.Geo);
}
},
leftArea: { // alert when we have left the area
type: "Exit",
circle: {
longitude: pos.coords.longitude,
latitude: pos.coords.latitude,
radius: 200
},
callback: function() {
window.alert('Left the area');
}
},
dwellArea: { // alert when we have stayed in the vicinity for 3 seconds
type: "DwellInside",
circle: {
longitude: pos.coords.longitude,
latitude: pos.coords.latitude,
radius: 50
},
dwellingTime: 3000,
callback: function() {
window.alert('Still in the vicinity');
}
}
}
};
WL.Device.startAcquisition({ Geo: geoPolicy }, triggers, { Geo: alertOnGeoAcquisitionErr } );
},
function(geoErr) {
alertOnGeoAcquisitionErr(geoErr);
// try again:
getFirstPositionAndTrack();
},
geoPolicy
);
Also, here are the errors I get from LogCat(debug):
06-04 15:42:00.243: D/NONE(3962): wlclient init success
06-04 15:42:03.602: D/WL gps Listener: The location has been
updated!
06-04 15:42:03.602: D/WL gps Listener: Acquired location age:
17306 milliseconds. More than maximumAge of 10000 milliseconds.
Ignoring.
06-04 15:42:03.602: D/WL gps Listener: The status of the
provider gps has changed
06-04 15:42:03.602: D/WL gps Listener: gps is
TEMPORARILY_UNAVAILABLE
06-04 15:43:03.509: D/CordovaLog(3962):
file:///android_asset/www/default/worklight/worklight.js: Line 12769 :
Uncaught ReferenceError: PositionError is not defined
06-04 15:43:03.509: E/Web Console(3962): Uncaught ReferenceError:
PositionError is not defined at
file:///android_asset/www/default/worklight/worklight.js:12769
06-04 15:43:04.040: D/CordovaActivity(3962): Paused the application!
To summarize, the app gets my GPS coordinates initially, but then immediate throws "gps is TEMPORARILY_UNAVAILEABLE", and that process repeats. Perhaps my phone can't handle Live Tracking? Although live tracking works in Google maps on my device.
file:///android_asset/www/default/worklight/worklight.js: Line 12769 :
Uncaught ReferenceError: PositionError is not defined
This issue was resolved in later versions of Worklight; my suggestion for you, if possible, is to upgrade your Studio installation to either the latest v6.2.0.01 release or even v6.3.
If you require it in v6.1, try upgrading to the latest iFix for 6.1.0.1 from IBM Fix Central.