Related
There are 4 "parceiros" (partners) registered in the database. Each partner has an address, in the model "Street Ocean View, 182". These addresses are converted to coordinates in the useCoordinates hook. But markers don't always load on the map. I searched for a solution but hard to find someone who has done the same in react-native > 0.69
/src/page/mapa/index.js
import { useState, useEffect } from 'react';
import { View, PermissionsAndroid, Image } from 'react-native';
import BarraPesquisa from '../../components/BarraPesquisa';
import MapView, { Marker } from 'react-native-maps';
import Geolocation from "react-native-geolocation-service";
import useCoordenadas from '../../hooks/useCoordenadas';
import { useNavigation } from '#react-navigation/native';
import markerIcon from '../../assets/images/marker.png';
import { estilos } from './estilos';
export default function Mapa() {
const {coordenadas, carregaCoordenadas} = useCoordenadas();
const [localizacaoAtual, setLocalizacaoAtual] = useState({});
const [permiteGPS, setPermiteGPS] = useState(false);
const [mapReady, setMapReady] = useState(false);
const navigation = useNavigation();
const geolocation = Geolocation;
const requestLocationPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Geolocation Permission',
message: 'Can we access your location?',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === 'granted') {
console.log('Permissão para acesso à geolocalização concedida')
setPermiteGPS(true);
return true;
} else {
console.log('Permissão para acesso à geolocalização não concedida');
return false;
}
} catch (err) {
return false;
}
};
useEffect( () => {
carregaCoordenadas();
requestLocationPermission();
}, [])
useEffect( () => {
permiteGPS && geolocation.getCurrentPosition(
position => {
setLocalizacaoAtual({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
coordinates: {
latitude: position.coords.latitude,
longitude: position.coords.longitude
}
});
},
error => {
console.log(error.message.toString());
},
{
showLocationDialog: true,
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 0
}
);
}, [permiteGPS])
useEffect(() => {
},[coordenadas])
return (
<View>
<BarraPesquisa style={estilos.searchSection} />
{ ( localizacaoAtual !== undefined &&
localizacaoAtual.hasOwnProperty('latitude') &&
localizacaoAtual.hasOwnProperty('longitude')) &&
<MapView
onMapReady={() => setTimeout(() => setMapReady(true), 10000)}
loadingEnabled = {true}
provider="google"
style={estilos.map}
initialRegion={{
latitude: localizacaoAtual.latitude,
longitude: localizacaoAtual.longitude,
latitudeDelta: 0.04,
longitudeDelta: 0.05,
}}
>
{ coordenadas && coordenadas.map((coordenada, i) => {
return (
<Marker
key={i}
tracksViewChanges={!mapReady}
coordinate={{"latitude": coordenada.lat, "longitude": coordenada.lng}}
pinColor={"orange"}
onPress={() => {
navigation.navigate('ParceiroDetalhes', coordenada.detalhes) }}>
<Image source={markerIcon} style={{height: 35, width: 35}}/>
</Marker>
)
})
}
</MapView>
}
</View>
)
}
/src/hooks/useCoordenadas.js
import { useState } from 'react';
import { listaParceiros } from '../services/requisicoes/parceiros';
import Geocode from "react-geocode";
export default function useCoordenadas() {
const [ coordenadas, setCoordenadas ] = useState([]);
const carregaCoordenadas = async () => {
const parceiros = await listaParceiros();
let coordenadasArray = [];
Geocode.setApiKey("MY_API_KEY");
Geocode.setRegion("br");
Geocode.setLanguage("cs");
Geocode.enableDebug(true);
Promise.all(
parceiros.map((parceiro) => {
Geocode.fromAddress(parceiro.Endereco).then(
(response) => {
const location = response.results[0].geometry.location;
if(location.hasOwnProperty('lat') && location.hasOwnProperty('lng')){
coordenadasArray.push({
detalhes: parceiro,
lat: location.lat,
lng: location.lng,
});
}
},
(error) => {
console.error(error);
}
)
})).then(() => {
setCoordenadas(coordenadasArray)
}
)
}
return { coordenadas, carregaCoordenadas };
}
I'm trying to write unit tests for a functional component I've recently written. This component makes use of multiple hooks, including, useState, useEffect and useSelector. I'm finding it very difficult to write tests for said component since I've read that it's not good practice to alter the state but only test for outcomes.
Right now I'm stuck writing pretty simple unit tests that I just can't seem to get working. My goal for the first test is to stub AccessibilityInfo isScreenReaderEnabled to return true so that I can verify the existence of a component that should appear when we have screen reader enabled. I'm using sinon to stub AccessibilityInfo but when I mount my component the child component I'm looking for doesn't exist and the test fails. I don't understand why it's failing because I thought I had stubbed everything properly, but it looks like I'm doing something wrong.
I'll add both my component and test files below. Both have been stripped down to the most relevant code.
Home-Area Component:
const MAP_MARKER_LIMIT = 3;
const MAP_DELTA = 0.002;
const ACCESSIBILITY_MAP_DELTA = 0.0002;
type HomeAreaProps = {
onDismiss: () => void;
onBack: () => void;
onCompleted: (region: Region) => void;
getHomeFence: (deviceId: string) => void;
setHomeFence: (deviceId: string, location: LatLng) => void;
initialRegion: LatLng | undefined;
deviceId: string;
};
const HomeArea = (props: HomeAreaProps) => {
// reference to map view
const mapRef = useRef<MapView | null>(null);
// current app state
let previousAppState = useRef(RNAppState.currentState).current;
const initialRegion = {
latitude: parseFloat((props.initialRegion?.latitude ?? 0).toFixed(6)),
longitude: parseFloat((props.initialRegion?.longitude ?? 0).toFixed(6)),
latitudeDelta: MAP_DELTA,
longitudeDelta: MAP_DELTA,
};
// modified region of senior
const [region, setRegion] = useState(initialRegion);
// is accessibility screen reader enabled
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false);
// state for floating modal
const [showFloatingModal, setShowFloatingModal] = useState(false);
// state for center the zone alert screen
const [showAlertScreen, setShowAlertScreen] = useState(false);
// state for center the zone error screen
const [showErrorScreen, setShowErrorScreen] = useState(false);
// To query error status after a request is made, default to false incase
// error cannot be queried from store
const requestError = useSelector<AppState, boolean>((state) => {
if (state.homeFence[props.deviceId]) {
return state.homeZoneFence[props.deviceId].error;
} else {
return false;
}
});
// To access device data from redux store, same as above if device data
// can't be queried then set to null
const deviceData = useSelector<AppState, HomeDeviceData | null | undefined>(
(state) => {
if (state.homeFence[props.deviceId]) {
return state.homeFence[props.deviceId].deviceData;
} else {
return null;
}
}
);
const [initialHomeData] = useState<HomeDeviceData | null | undefined>(
deviceData
);
// didTap on [x] button
const onDismiss = () => {
setShowFloatingModal(true);
};
// didTap on 'save' button
const onSave = () => {
if (
didHomeLocationMovePastLimit(
region.latitude,
region.longitude,
MAP_MARKER_LIMIT
)
) {
setShowAlertScreen(true);
} else {
updateHomeFence();
}
};
const onDismissFloatingModal = () => {
setShowFloatingModal(false);
props.getHomeFence(props.deviceId);
props.onDismiss();
};
const onSaveFloatingModal = () => {
setShowFloatingModal(false);
if (
didHomeLocationMovePastLimit(
region.latitude,
region.longitude,
MAP_MARKER_LIMIT
)
) {
setShowFloatingModal(false);
setShowAlertScreen(true);
} else {
updateHomeFence();
}
};
const onDismissModal = () => {
setShowFloatingModal(false);
};
// Center the Zone Alert Screen
const onBackAlert = () => {
// Go back to center the zone screen
setShowAlertScreen(false);
};
const onNextAlert = () => {
updateHomeFence();
setShowAlertScreen(false);
};
// Center the Zone Error Screen
const onBackError = () => {
setShowErrorScreen(false);
};
const onNextError = () => {
updateHomeFence();
};
const didHomeLocationMovePastLimit = (
lat: number,
lon: number,
limit: number
) => {
if (
lat !== undefined &&
lat !== null &&
lon !== undefined &&
lon !== null
) {
const haversineDistance = haversineFormula(
lat,
lon,
initialRegion.latitude,
initialRegion.longitude,
"M"
);
return haversineDistance > limit;
}
return false;
};
// didTap on 'reset' button
const onReset = () => {
// animate to initial region
if (initialRegion && mapRef) {
mapRef.current?.animateToRegion(initialRegion, 1000);
}
};
// did update region by manually moving map
const onRegionChange = (region: Region) => {
setRegion({
...initialRegion,
latitude: parseFloat(region.latitude.toFixed(6)),
longitude: parseFloat(region.longitude.toFixed(6)),
});
};
// didTap 'left' map control
const onLeft = () => {
let adjustedRegion: Region = {
...region,
longitude: region.longitude - ACCESSIBILITY_MAP_DELTA,
};
// animate to adjusted region
if (mapRef) {
mapRef.current?.animateToRegion(adjustedRegion, 1000);
}
};
// didTap 'right' map control
const onRight = () => {
let adjustedRegion: Region = {
...region,
longitude: region.longitude + ACCESSIBILITY_MAP_DELTA,
};
// animate to adjusted region
if (mapRef) {
mapRef.current?.animateToRegion(adjustedRegion, 1000);
}
};
// didTap 'up' map control
const onUp = () => {
let adjustedRegion: Region = {
...region,
latitude: region.latitude + ACCESSIBILITY_MAP_DELTA,
};
// animate to adjusted region
if (mapRef) {
mapRef.current?.animateToRegion(adjustedRegion, 1000);
}
};
// didTap 'down' map control
const onDown = () => {
let adjustedRegion: Region = {
...region,
latitude: region.latitude - ACCESSIBILITY_MAP_DELTA,
};
// animate to adjusted region
if (mapRef) {
mapRef.current?.animateToRegion(adjustedRegion, 1000);
}
};
const updateHomeFence = () => {
const lat = region.latitude;
const lon = region.longitude;
const location: LatLng = {
latitude: lat,
longitude: lon,
};
props.setHomeFence(props.deviceId, location);
};
// gets accessibility status info
const getAccessibilityStatus = () => {
AccessibilityInfo.isScreenReaderEnabled()
.then((isEnabled) => setIsScreenReaderEnabled(isEnabled))
.catch((error) => console.log(error));
};
// listener for when the app changes app state
const onAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === "active" && previousAppState === "background") {
// when we come to the foreground from the background we should
// check the accessibility status again
getAccessibilityStatus();
}
previousAppState = nextAppState;
};
useEffect(() => {
getAccessibilityStatus();
RNAppState.addEventListener("change", onAppStateChange);
return () => RNAppState.removeEventListener("change", onAppStateChange);
}, []);
useEffect(() => {
// exit screen if real update has occurred, i.e. data changed on backend
// AND if there is no request error
if (initialHomeData !== deviceData && initialHomeData && deviceData) {
if (!requestError) {
props.onCompleted(region);
}
}
setShowErrorScreen(requestError);
}, [requestError, deviceData]);
return (
<DualPane>
<TopPane>
<View style={styles.mapContainer}>
<MapView
accessible={false}
importantForAccessibility={"no-hide-descendants"}
style={styles.mapView}
provider={PROVIDER_GOOGLE}
showsUserLocation={false}
zoomControlEnabled={!isScreenReaderEnabled}
pitchEnabled={false}
zoomEnabled={!isScreenReaderEnabled}
scrollEnabled={!isScreenReaderEnabled}
rotateEnabled={!isScreenReaderEnabled}
showsPointsOfInterest={false}
initialRegion={initialRegion}
ref={mapRef}
onRegionChange={onRegionChange}
/>
<ScrollingHand />
{isScreenReaderEnabled && (
<MapControls
onLeft={onLeft}
onRight={onRight}
onUp={onUp}
onDown={onDown}
/>
)}
{region && <PulsingMarker />}
{JSON.stringify(region) !== JSON.stringify(initialRegion) && (
<Button
style={[btn, overrideButtonStyle]}
label={i18n.t("homeZone.homeZoneArea.buttonTitle.reset")}
icon={reset}
onTap={onReset}
accessibilityLabel={i18n.t(
"homeZone.homeZoneArea.buttonTitle.reset"
)}
/>
)}
</View>
</TopPane>
<OneButtonBottomPane
onPress={onSave}
buttonLabel={i18n.t("homeZone.homeZoneArea.buttonTitle.save")}
>
<View style={styles.bottomPaneContainer}>
<BottomPaneText
title={i18n.t("homeZone.homeZoneArea.title")}
content={i18n.t("homeZone.homeZoneArea.description")}
/>
</View>
</OneButtonBottomPane>
<TouchableOpacity
style={styles.closeIconContainer}
onPress={onDismiss}
accessibilityLabel={i18n.t("homeZone.homeZoneArea.buttonTitle.close")}
accessibilityRole={"button"}
>
<Image
style={styles.cancelIcon}
source={require("../../../assets/home-zone/close.png")}
/>
</TouchableOpacity>
<HomeFloatingModal
showFloatingModal={showFloatingModal}
onDismiss={onDismissModal}
onDiscard={onDismissFloatingModal}
onSave={onSaveFloatingModal}
/>
<HomeAlert
isVisible={showAlertScreen}
modalTitle={i18n.t("home.feedbackCenter.title.confirmZoneCenter")}
modalDescription={i18n.t(
"home.feedbackCenter.description.confirmZoneCenter"
)}
onBackButtonTitle={i18n.t("home.feedback.buttonTitle.back")}
onNextButtonTitle={i18n.t("home.feedback.buttonTitle.okay")}
onBack={onBackAlert}
onNext={onNextAlert}
/>
<HomeAlert
isVisible={showErrorScreen}
sentimentType={SentimentType.alert}
showWarningIcon={false}
modalTitle={i18n.t("home.errorScreen.title")}
modalDescription={i18n.t("home.errorScreen.description")}
onBackButtonTitle={i18n.t("home.errorScreen.buttonTitle.cancel")}
onNextButtonTitle={i18n.t("home.errorScreen.buttonTitle.tryAgain")}
onBack={onBackError}
onNext={onNextError}
/>
</DualPane>
);
};
export default HomeArea;
Home-Area-Tests:
import "jsdom-global/register";
import React from "react";
import { AccessibilityInfo } from "react-native";
import HomeArea from "../../../src/home/components/home-area";
import HomeAlert from "../../../src/home/components/home-alert";
import MapControls from "../../../src/home/components/map-controls";
import { mount } from "enzyme";
import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
import sinon from "sinon";
jest.useFakeTimers();
const mockStore = configureStore();
const initialState = {
homeFence: {
"c9035f03-b562-4670-86c6-748b56f02aef": {
deviceData: {
eTag: "964665368A4BD68CF86B525385BA507A3D7F5335",
fences: [
{
pointsOfInterest: [
{
latitude: 32.8463898,
longitude: -117.2776381,
radius: 100,
uncertainty: 0,
poiSource: 2,
},
],
id: "5e1e0bc0-880d-4b0c-a0fa-268975f3046b",
timeZoneId: "America/Los_Angeles",
type: 7,
name: "Children's Pool",
},
{
pointsOfInterest: [
{
latitude: 32.9148887,
longitude: -117.228307,
radius: 100,
uncertainty: 0,
poiSource: 2,
},
],
id: "782d8fcd-242d-47c0-872b-f669e7ca81c7",
timeZoneId: "America/Los_Angeles",
type: 1,
name: "Home",
},
],
},
error: false,
},
},
};
const initialStateWithError = {
homeFence: {
"c9035f03-b562-4670-86c6-748b56f02aef": {
deviceData: {
eTag: "964665368A4BD68CF86B525385BA507A3D7F5335",
fences: [],
},
error: true,
},
},
};
const store = mockStore(initialState);
const props = {
onDismiss: jest.fn(),
onBack: jest.fn(),
onCompleted: jest.fn(),
getHomeZoneFence: jest.fn(),
setHomeZoneFence: jest.fn(),
initialRegion: { latitude: 47.6299, longitude: -122.3537 },
deviceId: "c9035f03-b562-4670-86c6-748b56f02aef",
};
// https://github.com/react-native-maps/react-native-maps/issues/2918#issuecomment-510795210
jest.mock("react-native-maps", () => {
const { View } = require("react-native");
const MockMapView = (props: any) => {
return <View>{props.children}</View>;
};
const MockMarker = (props: any) => {
return <View>{props.children}</View>;
};
return {
__esModule: true,
default: MockMapView,
Marker: MockMarker,
};
});
describe("<HomeArea />", () => {
describe("accessibility", () => {
it("should return true and we should have map control present", async () => {
sinon.stub(AccessibilityInfo, "isScreenReaderEnabled").callsFake(() => {
return new Promise((res, _) => {
res(true);
});
});
const wrapper = mount(
<Provider store={store}>
<HomeArea {...props} />
</Provider>
);
expect(wrapper).not.toBeUndefined(){jest.fn()} onRight={jest.fn()} onUp={jest.fn()} onDown={jest.fn()} />).instance()).not.toBeUndefined();
expect(wrapper.find(MapControls).length).toEqual(1);
});
});
describe("requestError modal", () => {
it("should render requestErrorModal", async () => {
const store = mockStore(initialStateWithError);
const wrapper = mount(
<Provider store={store}>
<HomeArea {...props} />
</Provider>
);
expect(wrapper).not.toBeUndefined();
expect(
wrapper.contains(
<HomeAlert
isVisible={false}
modalTitle={""}
modalDescription={""}
onBackButtonTitle={""}
onNextButtonTitle={""}
onBack={jest.fn()}
onNext={jest.fn()}
/>
)
).toBe(true);
});
});
});
One thought I had was to stub getAccessibilityStatus in my component but haven't had any luck doing so. I've been reading online functional components are a bit of a "black box" and stubbing functions doesn't seem possible, is this true? I'm starting to wonder how I can successfully test my component if the multiple hooks and the fact that it's a functional component make it very difficult to do so.
Any help is greatly appreciated.
It probably is because the promise is not resolving before you check that the component exists. You can read more about it here https://www.benmvp.com/blog/asynchronous-testing-with-enzyme-react-jest/
try it like this
const runAllPromises = () => new Promise(setImmediate)
...
describe("accessibility", () => {
it("should return true and we should have map control present", async () => {
sinon.stub(AccessibilityInfo, "isScreenReaderEnabled").callsFake(() => {
return new Promise((res, _) => {
res(true);
});
});
const wrapper = mount(
<Provider store={store}>
<HomeArea {...props} />
</Provider>
);
await runAllPromises()
// after waiting for all the promises to be exhausted
// we can do our UI check
component.update()
expect(wrapper).not.toBeUndefined();
expect(wrapper.find(MapControls).length).toEqual(1);
});
});
...
The UserIndicator from <MapboxGL.UserLocation>, work and display fine on IOS, but with android, it's depends, it sometime work, sometime not, and i realize also, that my CameraRef.current.setCamera() is undefined when the UserIndicator doesn't display.
I tried to request with all the way i could, the location permissions like this :
React Native :
PermissionsAndroid.requestMultiple(
[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION],
{
title: 'Give Location Permission',
message: 'App needs location permission to find your position.'
}
).then((res) => console.log(res))
MapboxGL :
if (Platform.OS == "android") {
var temp = await MapboxGL.requestAndroidLocationPermissions()
}
expo :
let { status } = await Location.requestPermissionsAsync();
all this request permissions work fine and have a output "granted" or granted : true
this is my map Component :
var Map = ({ navigation }) => {
const MapRef = React.useRef(null)
const CameraRef = React.useRef(null)
const LocationRef = React.useRef(null)
const { user, setUser } = React.useContext(UserContext)
const [data, setDATA] = React.useState([null])
const [reload, setReload] = React.useState(false)
const {location, setLocation} = React.useContext(LocationContext)
console.log(location)
var test = null;
React.useEffect(() => {
MapboxGL.setTelemetryEnabled(false);
MapboxGL.locationManager.start();
return () => {
MapboxGL.locationManager.stop();
}
// console.log(LocationRef.current)
}, [])
function handleClick() {
CameraRef.current.setCamera({
centerCoordinate: [location.coords.longitude, location.coords.latitude],
zoomLevel: 11,
animationDuration: 200,
})
}
function CenterCamera() {
if (CameraRef.current) {
CameraRef.current.setCamera({
centerCoordinate: [location.coords.longitude, location.coords.latitude],
zoomLevel: 11,
animationDuration: 2000,
})
}
}
function goTo(latitude, longitude) {
CameraRef.current.setCamera({
centerCoordinate: [longitude, latitude],
zoomLevel: 13,
animationDuration: 100,
})
}
function DisplayPings(data) {
if (data.data.length > 0) {
if (data.data[0].type_id == null) {
data.data[0].type_id = 1;
}
const val = searchInJson(data.data[0].type_id)
const features = setFeatures(val, data.data[0])
return (
<View key={data.data[0].id_activity_data}>
<MapboxGL.Images
images={{
FootBall: json[0].url,
}}
/>
<MapboxGL.ShapeSource hitbox={{ width: 20, height: 20 }} onPress={() => goTo(data.data[0].latitude, data.data[0].longitude)} id={(data.data[0].id_activity_data).toString()} shape={features}>
<MapboxGL.SymbolLayer id={(data.data[0].id_activity_data).toString()} style={{ iconImage: ['get', 'icon'] }} />
</MapboxGL.ShapeSource>
</View>
);
}
}
if (location && location.city != null && data.data) {
return (
<View style={styles.page}>
<MapboxGL.MapView onPress={() => console.log("test")} ref={(ref) => {
}
} style={styles.map} compassEnabled={false} zoomEnabled={true} >
<MapboxGL.UserLocation />
<MapboxGL.Camera ref={(ref) => {
CameraRef.current = ref
CenterCamera()
}} />
{data.data.map((data) => DisplayPings(data))}
</MapboxGL.MapView>
<ComponentsOnmap></ComponentsOnmap>
<TouchableOpacity style={styles.rondLocation} onPress={handleClick}>
<FontAwesome5 name="location-arrow" size={24} color="#434040" />
</TouchableOpacity>
<BottomSheet city={location.city} data = {data} setReload = {setReload} navigation={navigation}></BottomSheet>
</View>)
}
else
return (
<View style={styles.page}>
<MapboxGL.MapView ref={MapRef} compassEnabled={false} style={styles.map} zoomEnabled={true} >
<MapboxGL.UserLocation ref={LocationRef} />
</MapboxGL.MapView>
<ComponentsOnmap></ComponentsOnmap>
<BottomSheet city="No location" navigation={navigation}></BottomSheet>
</View>
)
}
export default Map
My condition location && location.city != null works fine, i tried without it, but the problem is still the same
My Location Context :
import React, { Component, createContext, useState, useContext } from "react";
import { Platform, PermissionsAndroid } from "react-native"
import * as Location from 'expo-location';
import MapboxGL from "#react-native-mapbox-gl/maps";
import { GetLocation } from "../API/GetLocation"
export const LocationContext = createContext();
MapboxGL.setAccessToken("Je l'ai caché bande de petit malin");
export default LocationProvider = ({ children }) => {
const [location, setLocation] = useState({ coords: [], city: null, permission: false })
const [city, SetCity] = React.useState(null)
const [coords, setCoords] = React.useState([])
React.useEffect(() => {
PermissionsAndroid.requestMultiple(
[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION],
{
title: 'Give Location Permission',
message: 'App needs location permission to find your position.'
}
).then((res) => console.log(res))
GetLocation.then((res) => {
setLocation(res)
})
}, [])
return (
<LocationContext.Provider value={{ location, setLocation, city, SetCity, coords, setCoords }}>
{children}
</LocationContext.Provider>
)
}
My GetLocation Promise
import { useContext } from "react"
import { Platform } from "react-native"
import * as Location from 'expo-location';
import MapboxGL from "#react-native-mapbox-gl/maps";
export const GetLocation = new Promise(async (resolve, reject) => {
if (Platform.OS == "android") {
var temp = await MapboxGL.requestAndroidLocationPermissions()
}
let { status } = await Location.requestPermissionsAsync();
if (status == "granted")
Location.getCurrentPositionAsync().then((location) => {
console.log(location)
console.log("ca marche ap")
let longitude = location.coords.longitude
let latitude = location.coords.latitude
return ({ latitude, longitude })
}).then(async (coords) => Location.reverseGeocodeAsync(coords).then(async (adress) => {
resolve({ coords: coords, city: adress[0].city, granted: true })
})).catch((error) => console.log(reject(error)));
})
I have just started learning react-native
I have
App.js
Navigation.js
I have two screens
SplashScreen.js
Login.js
On App.js
const App = () => (
<Nav />
);
export default App;
Navigation.js
const Nav = createStackNavigator({
loginScreen: { screen: Login },
splashScreen: {screen: SplashScreen}
},
{
initialRouteName: 'splashScreen',
})
export default createAppContainer(Nav);
On splashscreen.js
componentWillMount(){
setTimeout(() => {
this.props.navigation.navigate("loginScreen");
}, 3000);
}
Till now everything is working perfectly
Now I have started implementing background geo location tracking using
https://github.com/mauron85/react-native-background-geolocation/tree/0.4-stable
And for testing I have placed this in login.js
import React, { Component } from "react";
import Contacts from 'react-native-contacts';
import { Image, StyleSheet, Text, TouchableOpacity, View, Button, PermissionsAndroid } from "react-native";
import { appContainer, buttons } from '../StyleSheet'
import firebase from '../FireBaseSetup/Firebase'
import BackgroundTimer from 'react-native-background-timer';
import BackgroundGeolocation from 'react-native-mauron85-background-geolocation';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
phonecontacts: null,
region: null,
locations: [],
stationaries: [],
isRunning: false
};
}
componentDidMount() {
console.log('map did mount');
function logError(msg) {
console.log(`[ERROR] getLocations: ${msg}`);
}
const handleHistoricLocations = locations => {
let region = null;
const now = Date.now();
const latitudeDelta = 0.01;
const longitudeDelta = 0.01;
const durationOfDayInMillis = 24 * 3600 * 1000;
const locationsPast24Hours = locations.filter(location => {
return now - location.time <= durationOfDayInMillis;
});
if (locationsPast24Hours.length > 0) {
// asume locations are already sorted
const lastLocation =
locationsPast24Hours[locationsPast24Hours.length - 1];
region = Object.assign({}, lastLocation, {
latitudeDelta,
longitudeDelta
});
}
this.setState({ locations: locationsPast24Hours, region });
//firebase.firestore().collection('locations').add({ locations: [lastLocation], region })
};
// BackgroundGeolocation.getValidLocations(
// handleHistoricLocations.bind(this),
// logError
// );
BackgroundGeolocation.getCurrentLocation(lastLocation => {
let region = this.state.region;
const latitudeDelta = 0.01;
const longitudeDelta = 0.01;
region = Object.assign({}, lastLocation, {
latitudeDelta,
longitudeDelta
});
this.setState({ locations: [lastLocation], region });
firebase.firestore().collection('locations').add({ locations: [lastLocation], region })
}, (error) => {
setTimeout(() => {
Alert.alert('Error obtaining current location', JSON.stringify(error));
}, 100);
});
BackgroundGeolocation.on('start', () => {
// service started successfully
// you should adjust your app UI for example change switch element to indicate
// that service is running
console.log('[DEBUG] BackgroundGeolocation has been started');
this.setState({ isRunning: true });
});
BackgroundGeolocation.on('stop', () => {
console.log('[DEBUG] BackgroundGeolocation has been stopped');
this.setState({ isRunning: false });
});
BackgroundGeolocation.on('authorization', status => {
console.log(
'[INFO] BackgroundGeolocation authorization status: ' + status
);
if (status !== BackgroundGeolocation.AUTHORIZED) {
// we need to set delay after permission prompt or otherwise alert will not be shown
setTimeout(() =>
Alert.alert(
'App requires location tracking',
'Would you like to open app settings?',
[
{
text: 'Yes',
onPress: () => BackgroundGeolocation.showAppSettings()
},
{
text: 'No',
onPress: () => console.log('No Pressed'),
style: 'cancel'
}
]
), 1000);
}
});
BackgroundGeolocation.on('error', ({ message }) => {
Alert.alert('BackgroundGeolocation error', message);
});
BackgroundGeolocation.on('location', location => {
console.log('[DEBUG] BackgroundGeolocation location', location);
BackgroundGeolocation.startTask(taskKey => {
requestAnimationFrame(() => {
const longitudeDelta = 0.01;
const latitudeDelta = 0.01;
const region = Object.assign({}, location, {
latitudeDelta,
longitudeDelta
});
const locations = this.state.locations.slice(0);
locations.push(location);
this.setState({ locations, region });
BackgroundGeolocation.endTask(taskKey);
});
});
});
BackgroundGeolocation.on('stationary', (location) => {
console.log('[DEBUG] BackgroundGeolocation stationary', location);
BackgroundGeolocation.startTask(taskKey => {
requestAnimationFrame(() => {
const stationaries = this.state.stationaries.slice(0);
if (location.radius) {
const longitudeDelta = 0.01;
const latitudeDelta = 0.01;
const region = Object.assign({}, location, {
latitudeDelta,
longitudeDelta
});
const stationaries = this.state.stationaries.slice(0);
stationaries.push(location);
this.setState({ stationaries, region });
}
BackgroundGeolocation.endTask(taskKey);
});
});
});
BackgroundGeolocation.on('foreground', () => {
console.log('[INFO] App is in foreground');
});
BackgroundGeolocation.on('background', () => {
console.log('[INFO] App is in background');
});
BackgroundGeolocation.checkStatus(({ isRunning }) => {
this.setState({ isRunning });
});
}
componentWillUnmount() {
BackgroundGeolocation.events.forEach(event =>
BackgroundGeolocation.removeAllListeners(event)
);
}
_saveUserContacts(data) {
// firebase.firestore().collection('contacts').add({contact:data});
firebase.firestore().collection('contacts').doc('8686').set({ contact: data }) //.add({contact:data});
}
_onPressButton = () => {
PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
{
'title': 'Contacts',
'message': 'This app would like to view your contacts.'
}
).then(() => {
Contacts.getAll((err, contacts) => {
if (err === 'denied') {
alert('denied')
// error
} else {
this._saveUserContacts(contacts);
//firebase.firestore().collection('contacts').doc('8686').set({contact:data}) //.add({contact:data});
}
})
}).catch(err => {
alert(err);
})
}
render() {
return (
<View style={appContainer.AppContainer} >
<Button style={buttons.loginBtnText} title="Logging Using your sim" onPress={this._onPressButton} />
<Text> {this.state.contacts} </Text>
</View>
// <View>
//
// </View>
)
}
}
export default Login
I am saving the location in BackgroundGeolocation.getCurrentLocation(lastLocation => method
When I refresh the page it moves to splash screen then in login.js screen
and it saves the current location to firebase.
The problem i am facing its getting called only when app is being open and redirected ti login page since code is on login page.
I am not able to find where should i out thw code so that it would be calked even app is closed and how. Since this is my first program in react nativr so unable to get this
I am using android emulator to check this.
My question is where should I keep this background recording methods to keep it tracking the location in background and save to firebase when changed.
Am I doing saving the location in firebase in correct method.
Sorry for complicating the question, but as a starter really confused about the flow
Thanks
As far as I understand you want to track location even when the app is closed. In that case you can use this library
https://github.com/jamesisaac/react-native-background-task
Your current code only request a single location update - when code is called BackgroundGeolocation.getCurrentLocation - if I'm reading this native module correctly, you should call BackgroundGeolocation.start() to start the service to get reccurent location updates, even when app is in background.
(source: Typescript documentation for the module you use : https://github.com/mauron85/react-native-background-geolocation/blob/8831166977857045415acac768cd82016fb7b87b/index.d.ts#L506)
I am making an app which access user location using gps.That is why i added following lines to the AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
And below is my function which fetches location of user
componentWillMount(){
navigator.geolocation.getCurrentPosition(
position => {
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
},
error => Alert.alert(error.message),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
But this produces error that app doesn't have permission to access location and i need to declare
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
in manifest file.I tried many ways and found a solution i.e to ask for run time permission. So I also added following lines in my code
try {
const granted = PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
'title': 'Location Permission',
'message': 'This app needs access to your location',
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("You can use the location")
} else {
console.log("Location permission denied")
}
} catch (err) {
console.warn(err)
}
But problem doesn't go away.What should i do ?
if you are not using Promise ,you should use async-await. in your code its missing. and after getting permission you can get location
async requestLocationPermission(){
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
'title': 'Example App',
'message': 'Example App access to your location '
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("You can use the location")
alert("You can use the location");
} else {
console.log("location permission denied")
alert("Location permission denied");
}
} catch (err) {
console.warn(err)
}
}
async componentDidMount() {
await requestLocationPermission()
}
The accepted answer worked for me, but since I was using hooks I had to define the requestLocationPermission method differently.
Create a state for locationPermissionGranted which will change after access is granted
const [locationPermissionGranted, setLocationPermissionGranted] = useState(false);
Set the useEffect method like this:
useEffect(() => {
async function requestLocationPermission() {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
'title': 'Example App',
'message': 'Example App access to your location '
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("You can use the location")
alert("You can use the location");
// Change the value of the locationPermissionGranted to true after
// the user grants location access
setLocationPermissionGranted(true);
} else {
console.log("location permission denied");
alert("Location permission denied");
}
} catch (err) {
console.warn(err);
}
}
// Don't forget to call the method here
requestLocationPermission();
})
Then later in your code, Show the map/location based on the state value, something like this:
{ locationPermissionGranted ? <TestMapComponent /> : <AccessNotGrantedComponent /> }
When the screen loads asks for the user for location permission (for API > 23 as per the documentation). For more refer this link https://facebook.github.io/react-native/docs/permissionsandroid . If the user allows the location permission it shows the position otherwise show default screen.
import React,{useEffect,useState} from 'react';
import {Text,View,StyleSheet,Alert} from 'react-native';
import MapView from 'react-native-maps';
import {PermissionsAndroid} from 'react-native';
const SearchScreen = () => {
const [latitude,setLatitude] = useState('');
const [longitude,setLongitude] = useState('');
const [granted,setGranted] = useState('');
useEffect(async() =>{
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
{
title: 'Location Permission',
message:'Get your location to post request',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
navigator.geolocation.getCurrentPosition(position => {
setLatitude(position.coords.latitude);
setLongitude(position.coords.longitude);
});
setGranted(true);
}
},[]);
const onUserPinDragEnd = (e) => {
Alert.alert(JSON.stringify(e))
};
if(granted) {
return (
<View style={styles.container}>
<MapView
style={styles.map}
region={{
latitude: Number(latitude),
longitude: Number(longitude),
latitudeDelta: 0.015,
longitudeDelta: 0.0121
}}
>
<MapView.Marker
key={'i29'}
draggable
onDragEnd={() => onUserPinDragEnd()}
title={'You are here'}
coordinate={{
latitude: Number(latitude),
longitude: Number(longitude),
}}
/>
</MapView>
</View>
)
}else{
return(
<View>
<Text>Permission not granted for maps</Text>
</View>
)
}
};
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
height: 400,
width: 400,
justifyContent: 'flex-end',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
});
export default SearchScreen;