Async function not returning on android - javascript

I'm having a problem with an async function not returning when running on android whereas it returns normally when run on iOS.
This is the function:
_getLocationAsync = async () => {
let {status} = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
this.setState({
errorMessage: 'Permission to access location was denied',
});
}
let location = await Location.getCurrentPositionAsync({});
this.setState({location});
return location;
};
and I'm using it in another function here:
async fetchMarkers(settings ){
console.log("fetchMarkers");
// console.log(settings);
this.setState({listLoading: true});
let location = await this._getLocationAsync();
console.log("location is ", location);
....
....
}
This line is not returning in android, but it returns in ios. In android I tried logging the value of location just before returning it in _getLocationAsync and it logs a defined and correct object, I'm wondering why it's failing to return it then:
let location = await Location.getCurrentPositionAsync({});
I'm using React Native 0.53

I think there are some reasons that Android can't get location.
I'm using this location option, anh it
works well on android
// how accuracy should get location be
const GET_LOCATION_OPTIONS = {
enableHighAccuracy: false,
timeout: 20000,
maximumAge: 1000,
};
navigator.geolocation.getCurrentPosition(
(position) => {
const location = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
};
if (callback) { callback(location); }
},
(err) => {
if (callback) { callback(null); }
},
GET_LOCATION_OPTIONS,
);
Ref: https://facebook.github.io/react-native/docs/geolocation.html

maybe it's a permission problem,
check whether the app does apply the position permisson

Related

How to Periodically Check Whether Data from API Has Changed

I'm fetching weather data from OpenWeather API for a given location and want to check, every minute, whether that data is still current (and if not, change it). I've used setInterval but the data doesn't seem to update every minute--here are the functions in question.
In the controller...
const controlStation = async function (station) {
try {
// Updates weather given station
const weather = await model.updateWeather(station);
// Periodically checks if weather data is current
// If not, updates weather
let checkWeather = await model.checkWeather(station);
setInterval(checkWeather, 1 * MINUTES);
// Renders weather
weatherView.render(model.stations[station], weather);
} catch (err) {
console.log(err);
}
};
controlStation("fbi");
In the model...
export const state = {};
export const stations = {
fbi: {
name: "fbi",
city: "Sydney, Australia",
coordinates: [-33.5346, 151.12],
},
kutx: {
name: "kutx",
city: "Austin, Texas, United States of America",
coordinates: [30.1721, -97.4402],
},
cism: {
name: "cism",
city: "Montreal, Quebec, Canada",
coordinates: [45.3023, -73.3644],
},
};
export const updateWeather = async function (station) {
try {
const [lat, lng] = stations[station].coordinates;
const url = `${API_WEATHER_URL}lat=${lat}&lon=${lng}&appid=${API_WEATHER_KEY}&units=imperial`;
const data = await fetch(url);
const weather = await data.json();
state.station = station;
state.weather = weather;
return weather;
} catch (err) {
console.error(err);
}
};
export const checkWeather = async function (station) {
try {
console.log("Checking weather!");
const needsUpdate = false;
const prev = state;
console.log("prev", prev.weather);
const cur = await updateWeather(state.station);
console.log("cur", cur);
if (
prev.weather.wind.speed !== cur.wind.speed ||
prev.weather.wind.dir !== cur.wind.dir ||
prev.weather.main.temp !== cur.main.temp ||
prev.weather.weather[0].description !== cur.weather[0].description
) {
console.log("Changing now");
needsUpdate = true;
} else console.log(`They were same at ${Date.now()}`);
return needsUpdate;
} catch (err) {
console.error(err);
}
};
I know I still need to do something if the weather data has changed and is different than what's in state, but I don't even see it making a new comparison through the checkWeather function every minute.
/////////////////
UPDATE--
I discovered that the issue was that async functions are incompatible with vanilla JS setInterval. There's a node package for creating setInterval with an async callback function but I don't know Node yet so instead I grabbed this workaround off another StackOverflow answer.
async function execute1() {
while (true) {
await new Promise((resolve) => setTimeout(resolve, 2 * MINUTES));
await model.checkWeather(station);
}
}
execute1();
Now my program is successfully checking the results from a new API call to the data stored in state. It recognizes when things have changed and when they haven't and now I'm going to update state when the weather conditions have changed. Thanks all for the help!

Passing parameter to custom hook

There is lot of documentation about the hooks, but I don't seem to understand them well enough.
Code
const App = () => {
const [markers, setMarkers] = useState(null);
const fetchMarkers = async () => {
const res = await axios.get(`${process.env.REACT_APP_BASE_URL}/markers`);
setMarkers(res.data);
};
useEffect(() => {
fetchMarkers();
}, []);
useInterval(() => {
fetchMarkers();
}, 10000);
}
I've made a custom hook which returns an object, I can call it like this:
const location = useGeoLocation();
I would like to add markers as parameter to useGeoLocation hook. This can be achieved easily just by calling the function on code and adding markers.
Objective
My goal is to make sure that markers contains some value before calling useGeoLocation. Right now, geoLocation hooks parameter returns value of null, because application didn't have time to fetchMarkers and update the state.
I've tried
My first thought was to do something like this:
if (markers) {
const location = useGeoLocation(markers);
}
But of course, like stated in rules of hooks, hooks can't be used conditionally.
UPDATED 10.1.2022 - useGeoLocation hook
Here I'm fetching users location. On success, I will compare users location to an array of markers & find the closest one to user.
On error, I will return only a predefined location.
This doesn't work because onSuccess is executed before there is any data in markers(null). I'm not sure how to use if - else statement here, because I'm also using useEffect & useState.
Can someone help?
const useGeoLocation = (markers) => {
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" },
});
const onSuccess = (location) => {
// User location succesfully fetched, converting data format.
const targetPoint = turf.point([
location.coords.latitude,
location.coords.longitude,
]);
// Fetching data from markers parameter and converting data format.
var arr = [];
markers.map((marker) => arr.push(turf.point(marker.location)));
var points2 = turf.featureCollection(arr);
// Calculating which marker is nearest to targetPoint.
var nearest = turf.nearestPoint(targetPoint, points2);
// Setting location of nearest marker to usestate.
setLocation({
loaded: true,
coordinates: {
lat: nearest.geometry.coordinates[0],
lng: nearest.geometry.coordinates[1],
},
});
};
const onError = () => {
setLocation({
loaded: true,
coordinates: {
lat: 65.024335,
lng: 27.277089,
},
});
};
useEffect(() => {
if (!("geolocation" in navigator)) {
onError();
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}, []);
return location;
};
Add markers in the dependency array of useEffect hook of useGeoLocation and call navigator.geolocation.getCurrentPosition(onSuccess, onError) if the markers are available and in the else part you can call setLocation().
const useGeoLocation = (markers) => {
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" },
});
useEffect(() => {
if (markers) {
if (!("geolocation" in navigator)) {
onError();
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
} else {
setLocation();
}
}, [markers]);
return location;
};

async/await with Try and Catch block

I am fetching a user's location from Expo. While catch block always executes first and later location is still fetched correctly. Though, catch should happen only if the location was not fetched.
PS: It happens only when I am testing the app on real device under a Tunnel connection. However, works fine on the Emulator.
I don't see anything wrong with the Code though.
Please comment.
useEffect(() => {
const verifyPermissions = async () => {
const result = await Permissions.askAsync(Permissions.LOCATION);
if (result.status !== 'granted') {
Alert.alert(
'Insufficient permissions!',
'You need to grant location permissions to use this app.',
[{ text: 'Okay' }]
);
return false;
}
return true;
};
(async () => {
const hasPermission = await verifyPermissions();
if (!hasPermission) {
return;
}
try {
const location = await Location.getCurrentPositionAsync({});
setPickedLocation({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
});
} catch (err) {
Alert.alert('Could not fetch location!', 'Please try again.', [
{ text: 'Okay' },
]);
}
})();
}, []);

Undefined is not an object (evaluating 'navigator.permissions.query')

I am getting this error when trying to access my website on an iPhone 7, with a white bank screen (the main screen loads fine, but then I get this at the net screen after I click something.
I assume this is what it's talking about:
useEffect(() => {
navigator.permissions
.query({ name: "microphone" })
.then((permissionStatus) => {
setMicrophonePermissionGranted(permissionStatus.state === "granted");
permissionStatus.onchange = function () {
setMicrophonePermissionGranted(this.state === "granted");
};
});
navigator.permissions.query({ name: "camera" }).then((permissionStatus) => {
setCameraPermissionGranted(permissionStatus.state === "granted");
permissionStatus.onchange = function () {
setCameraPermissionGranted(this.state === "granted");
};
});
}, []);
How do I fix this?
You need to check permission APIs availability and then if not available - query standard APIs.
Here is the location example:
Permissions API
Navigation API
if ( navigator.permissions && navigator.permissions.query) {
//try permissions APIs first
navigator.permissions.query({ name: 'geolocation' }).then(function(result) {
// Will return ['granted', 'prompt', 'denied']
const permission = result.state;
if ( permission === 'granted' || permission === 'prompt' ) {
_onGetCurrentLocation();
}
});
} else if (navigator.geolocation) {
//then Navigation APIs
_onGetCurrentLocation();
}
function _onGetCurrentLocation () {
navigator.geolocation.getCurrentPosition(function(position) {
//imitate map latlng construct
const marker = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
})
}
Permissions.query() is marked as an experimental feature as of June 2021 https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query.
As of today, that traduces into that you'll need to implement two UIs / flows; one capable of supporting fancy flows to tell the user how to proceed, and the other one more standard, using try / catch blocks. Something like:
useEffect(() => {
requestPermissions();
}, []);
const requestPermissions = async () => {
try {
handlePermissionsGranted();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
startRecording();
} catch {
...
}
};
const handlePermissionsGranted = async () => {
if (navigator.permissions && navigator.permissions.query) {
const permissions = await navigator.permissions.query({name: 'microphone'});
permissions.onchange = () => {
setMicrophonePermissionGranted(permissions === 'granted');
};
}
};
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
...
} catch {
... << if you reach this catch means that either the browser does not support webrtc or that the user didn't grant permissions
}
};
I was trying to check for the mic and camera permissions from iOs devices and through the Facebook browser, which I guess makes the whole thing fail, as these don't exist in those environments.
Once I've moved that query to the component that only loads if it is not a mobile device, my error fixed.

Concurrent async requests - keep track of necessary timeout

With Nominatim requests I need to keep a timeout of 1500ms between every requests. How do I handle that if multiple concurrent running processes keep firing on that function?
Code:
osmService:
export const getLocation = async (zipCode, street) => {
if (!zipCode) {
return {
address: null,
cacheHit: false,
};
}
const cacheData = await getAddress(zipCode, street);
if (cacheData && cacheData.length === 1) {
return { address: cacheData[0].address, cacheHit: true };
} if (cacheData && cacheData.length > 1) {
logger.error('Found multiple address, this should not be', zipCode + street);
}
try {
const responseObj = await getNominatimLocation(zipCode, street);
if (responseObj) {
const returnObj = {
...responseObj.address,
lat: responseObj.lat,
lon: responseObj.lon,
displayName: responseObj.display_name,
};
await insertAddress(zipCode, street, null, returnObj);
return {
address: returnObj,
cacheHit: false,
};
}
return {
address: null,
cacheHit: false,
};
} catch (ex) {
logger.error(`Error getting location from ${zipCode} ${street}`, ex);
}
return {
address: null,
cacheHit: false,
};
};
As you can see I have a caching layer in between. So when the request was hit by cache I do not need to wait 1500ms.
export const getNominatimLocation = async (zipCode, street, query, retries = 0) => {
if (retries > 5) {
return null;
}
try {
const qs = {
format: 'json',
q: query,
postalcode: zipCode,
addressdetails: 1,
country: 'Deutschland',
street,
'accept-language': 'de',
};
const response = await requestPromise()
.get({
url: OSM_SEARCH,
qs,
timeout: 12000,
headers: {
'User-Agent': 'xxxxxxx',
},
});
return JSON.parse(response)[0];
} catch (ex) {
logger.info(`Nominatim timed out - retry ${retries}`, ex);
await timeout(9000);
return await getNominatimLocation(zipCode, street, query, retries + 1);
}
};
Since Nominatim often times out I need to do this recursive call (does not need to be recursive - was just easier).
Now let's suppose I have following jobs (backend engine) that want to get locations
const jobA = asnyc () => {
const {address, cacheHit} = await getLocation(10243);
if(!cacheHit){
await timeout(1500)
}
}
const jobB = asnyc () => {
const {address, cacheHit} = await getLocation(10245);
if(!cacheHit){
await timeout(1500)
}
}
const startJobs = async () => {
Promise.all([jobA(),jobB()])
console.log('all jobs done');
}
The jobs partially represent my current code structure. In my code the jobs do more (call other services etc.).
Now when I have this layout - how can I make sure to keep 1500ms between every Nominatim call when there is no cacheHit?
You could use a lock that only unlocks every 1.5 seconds:
let lock = Promise.resolve();
let aquireLock = () => (lock = lock.then(() => new Promise(res => setTimeout(res, 1500))));
Then
await aquireLock();
// will only run every 1.5 seconds

Categories