I need some help please.
in one of my pages I call a component. However, it is not displayed. I'm not sure what's wrong, and how to find out where exactly the error came from. I know that the code contained in the component is good since I am calling it in another screen. I wonder if it's not because of 'parent' use
<View style={{width: "66.66%"}}>
<MapView parent={this} />
<Text>{"ICIII"}</Text>
</View>
There is just the text that is displayed (the text was here just to test). I hope someone can help me try to understand where the problem come from, I know I have a lot to learn so thank you for your time and explanations.
This is the component MapView :
export default class MapView extends Component {
constructor(props) {
super(props);
this.state = {
progress: new Animated.Value(0),
onLoading: true,
height: 0,
user_id: 0,
lang: "en",
access_token: "",
stats : "",
screen_updated: 0,
map_request_periods: "",
map_request_periods_title: "",
map_request_periods_val: "*"
};
}
performMessageFromWebView(e) {
if (e.nativeEvent && e.nativeEvent.data) {
let id = e.nativeEvent.data.replace('flightId=', '');
if (parseInt(id) > 0) {
this.props.navigation.navigate("UpdateTripsForm", {flightId: parseInt(id)}); // Open Screen UpdateTripForm.js
return true;
}
}
return false;
}
initUserData = async () => {
let user_id = await retrieveProfileUserId();
let lang = await retrieveAppLang();
let user_stats = await getUserFlightStats();
let access_token = await renewAccessToken();
if (user_id && lang && access_token && parseInt(user_id) > 0) {
this.setState({
user_id: parseInt(user_id),
lang: lang,
stats: user_stats,
access_token: access_token,
});
}
};
async UNSAFE_componentWillMount() {
this.initUserData();
}
async componentDidMount() {
// Refresh data
this.props.navigation.addListener("didFocus", async (payload) => {
let user_id = await retrieveProfileUserId();
let user_stats = await getUserFlightStats();
let access_token = await renewAccessToken();
if (user_id && access_token && parseInt(user_id) > 0) {
this.setState({
screen_updated: this.state.screen_updated + 1,
user_id: parseInt(user_id),
stats: user_stats,
access_token: access_token,
map_request_periods: "", // Force reinitialize choice
map_request_periods_title: "",
map_request_periods_val: "*"
});
console.log(Device.DeviceType);
}
// Force PORTRAIT orientation
await ScreenOrientation.getOrientationAsync().then(
(data_orientation) => {
if (parseInt(data_orientation) == parseInt(ScreenOrientation.Orientation.LANDSCAPE_LEFT) || parseInt(data_orientation) == parseInt(ScreenOrientation.Orientation.LANDSCAPE_RIGHT)) {
ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.PORTRAIT_UP
);
}
}
);
});
// const data = this.props.navigation.getParam('this.selectedStartDate', 'this.selectedEndDate');
// this.setState({ this.state.selectedStartDate, this.state.selectedEndDate});
Animated.timing(this.state.progress, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true,
}).start();
if (this.state.onLoading == true) {
setTimeout(
function () {
this.setState({ onLoading: false });
}.bind(this),
3000
);
}
}
render() {
return (
<View>
<WebView
geolocationEnabled={true}
javaScriptEnabled={true}
scrollEnabled={false}
overScrollMode="never"
source={{
uri:
"https://www.blzlva.org/fr/mestrips" +
"?society_id=" + API_SOCIETYID +
"&user_id=" + this.state.user_id +
"&lang=" + this.state.lang +
"&hidezoom=1" +
"&access_token=" + this.state.access_token +
"&screen=" + this.state.screen_updated
}}
originWhitelist={[
"https://www.blzblz.org",
"https://www.blabla.com"
]}
scalesPageToFit={true}
onMessage={(m) => this.performMessageFromWebView(m)}
style={{ marginHorizontal: 0, backgroundColor: "transparent" }}
/>
</View>
);
}
}
Import the map component which page you want to use it like following,
import Map from '../../map.js'
then use the component inside a view like following,
<View style={{width:100px}}>
<Map/>
</View>
Related
In the screen that I'm trying to put up. I get the ID of a product. The previous screen being a list of products, which come from an API. I get this id thanks to props.data
The console.log ('props', this.props.data) works fine and returns the correct ID to me regardless of the product clicked.
With the ID of this product, I want to find the detailed information of this product (the reference, the family, the price, the stock etc.).
I created this function to call the product detail via my API.
initListData = async () => {
if (parseInt(this.state.productId) > 0) {
let product_data = await getProduct(this.state.productId);
console.log('product_data', product_data)
this.setState({
displayArray: product_data,
loadMoreVisible: (product_data.length >= 15 ? true : false),
currentPage: 2
});
}
};
I think the problem is that the displayArray[] is empty so
let product_data = await getProduct(this.state.productId);
Doesn't work.
And I get this error: Cannot update a component from inside the function body of a different component
Can you explain to me what's wrong ?
Full code
export default class Information extends Component {
constructor(props) {
super(props);
this.state = {
productId: this.props.data,
displayArray: [],
}
console.log('props', this.props.data) // ok, ça fonctionne, on récupère bien l'ID du produit cliqué
};
initListData = async () => {
if (parseInt(this.state.productId) > 0) {
let product_data = await getProduct(this.state.productId);
console.log('product_data', product_data)
this.setState({
displayArray: product_data,
loadMoreVisible: (product_data.length >= 15 ? true : false),
currentPage: 2
});
}
};
async UNSAFE_componentWillMount() {
this.initListData();
}
render() {
console.log('ça c\'est data = ', this.props.data );
console.log('ça c\'est les props =', this.props );
console.log('ça c\'est le state = ', this.state );
return (
<ScrollView contentContainerStyle={{flex: 1}}>
{
this.state.displayArray.map((item, i) => (
<ListItem bottomDivider>
<Icon name='flight-takeoff' />
<ListItem.Content>
<ListItem.Title style={{color: '#d35400'}}>{item.name}</ListItem.Title>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.family")}: {item.family_id}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.reference")}: {item.reference}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.id")}: {item.id}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.cost")}: {item.cost}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.description")}: {item.description}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.stock")}: {item.stock_status}
</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView>
);
}
}
getProduct function : [I just have to hide the real url]
export async function getProduct(product_id) {
const abortController = new AbortController();
let user_id = await retrieveProfileUserId();
let lang = await retrieveAppLang();
let access_token = await renewAccessToken();
let result = {};
if (parseInt(product_id) > 0 && access_token != '' && parseInt(user_id) > 0) {
try {
let response = await fetch(
API_URL +
"/products/" + product_id +
"?user_id=" + user_id +
"&society_id=" + API_SOCIETYID +
"&access_token=" + access_token +
"&lang=" + lang,
{
method: "GET",
signal: abortController.signal,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + API_SECRETKEY,
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0"
}
}
)
.then(response => response.json())
.then(responseData => {
if (responseData.status == 200 && responseData.data) {
console.log("getProduct()::success", responseData.data);
result = responseData.data;
} else if (
responseData.status >= 200 && responseData.status <= 404 &&
responseData.data.error && responseData.data.error.length >= 3
) {
// Handle error
throw responseData.data.error;
} else {
throw "error";
}
});
} catch (error) {
//console.log(error);
abortController.abort();
}
}
Try changing a few things around and it should work.
I also don't like setting state directly inside the fn, so I propose the following change:
initListData = async () => {
if (this.props.data != null) {
const productData = await getProduct(this.state.productId);
return productData; // You are missing this, this is vital!
}
};
async componentDidMount() {
const data = await this.initListData(); // don't forget await here if you're using async
if (data.id) {
this.setState((prevState) => ({
displayArray: [...prevState.displayArray, data],
loadMoreVisible: ..., // I don't know what do you want here, because again, you receive an object from your backend, not an array.
currentPage: 2
}));
}
}
I am not able to get an alert when the api response fails.I am trying to add the alert in where the actions are getting dispatched.Can we trigger an alert of we have redirect to a different page after the submission? Is there any other way to show the error msg when the api response fails.
const saveOPPInfo = () => {
this.setState({
_firstNameEmpty: isEmpty(_firstName),
_lastNameEmpty: isEmpty(_lastName),
_emailNotValid: !validateEmail(_email)
});
if (
this.isSaveAttempted ||
isEmpty(_firstName) ||
isEmpty(_lastName) ||
!validateEmail(_email)
) {
return;
}
this.isSaveAttempted = true;
this.setState({ _isFetching: true });
if (action === "ADD") {
const jsonPayload = [
{
email: _email,
firstName: _firstName,
lastName: _lastName,
isPrimary: false
}
];
addOtherPickupPerson({ purchaseContractId, jsonPayload }).then(() => {
GroceryNotifierApi.events().emitOnAction("saveOPPInfo", "");
});
} else {
const jsonPayload = {
email: _email,
firstName: _firstName,
lastName: _lastName,
isPrimary: false
};
editOtherPickupPerson({ purchaseContractId, jsonPayload, pickupPersonId: _id }).then(() => {
GroceryNotifierApi.events().emitOnAction("saveOPPInfo", "");
});
}
};
here is the part of jsx
<View
style={{
paddingLeft: 20,
paddingRight: 20,
paddingTop: 24
}}
>
{action === "ADD" ? (
<Body>
{OPP_ADD_OPP_DESC}
<Body
onPress={() => nativeNav.navigateToOPPLearnMore()}
style={{ textDecorationLine: "underline" }}
>
{OPP_LEARN_MORE}
</Body>
</Body>
) : (
<View />
)}
</View>
{_isFetching && (
<View style={{ height: 120 }}>
<CircularIndicator />
</View>
)}
<View style={styles.bottom}>
<PrimaryButton
style={styles.saveButton}
testID="save"
block
onPress={saveOPPInfo}
disabled={this.isSaveAttempted || _firstNameEmpty || _lastNameEmpty || _emailNotValid}
>
{isIOS ? "Save" : "SAVE"}
</PrimaryButton>
{action === "ADD" ? (
<View />
) : (
<LinkButton onPress={removeOPPInfo} disabled={this.isSaveAttempted}>
{OPP_REMOVE}
</LinkButton>
)}
</View>
</View>
);
}
this is how the action looks
export const addOtherPickupPersonRequest = createAction(ADD_OTHER_PICKUP_PERSON_REQUEST);
export const addOtherPickupPersonSuccess = createAction(ADD_OTHER_PICKUP_PERSON_SUCCESS);
export const addOtherPickupPersonFail = createAction(ADD_OTHER_PICKUP_PERSON_FAIL);
export const addOtherPickupPerson = ({ purchaseContractId, jsonPayload }) => {
return async function(dispatch) {
store.dispatch(addOtherPickupPersonRequest());
let response;
let responseJson;
try {
const startTime = new Date().getTime();
response = await fetchAddOtherPickupPerson(purchaseContractId, jsonPayload);
if (response.status >= 200 && response.status < 300) {
trackPerformance("checkout-add-opp-api", startTime, "performance-metric");
responseJson = await response.json();
store.dispatch(addOtherPickupPersonSuccess(responseJson));
return responseJson;
} else {
Alert.alert(
"Oops22!",
"Something is not working here. Please check back later.",
[{ text: "Okay" }],
{ cancelable: false }
);
await Promise.reject(err);
}
} catch (err) {
trackErrorEvent("checkout-add-opp-api", { ...defaultPayload({ err }) }, "api-error");
Alert.alert(
"Oops!",
"Something is not working here. Please check back later.",
[{ text: "Okay" }],
{ cancelable: false }
);
return store.dispatch(addOtherPickupPersonFail(err));
}
};
};
I want to show an alert when the response.status is not response.status >= 200 && response.status < 300)
I had been developing my app for Web, and it has been working properly. However, when I ran the same app within Expo / Android, I got this error. Hard to know what it is about from the description.
This is the full error message:
Cannot add a child that doesn't have a YogaNode to a parent without a measure function! (Trying to add a '[RCTVirtualText 507]' to a '[RCTView 509]')
Do you know what it could possibly be?
This seems to be the js file that is triggering it:
...
export class SubjectListAssignScreen extends React.Component {
state = {
subjectList: [],
subListLoading: true,
};
constructor(props) {
super(props);
};
scrollDimensions = [{
width: Math.round(Dimensions.get('window').width - 20),
maxHeight: Math.round(Dimensions.get('window').height - 200)
}];
...
_getSubjects = async(text) => {
try {
await this.setState({ subListLoading: true });
let lQueryRes = await API.graphql(graphqlOperation(cqueries.listSubjectsCustom, {}));
await console.log('==> Subjects Query');
await console.log(lQueryRes);
await this.setState({ subjectList: lQueryRes.data.listSubjects.items });
await this.setState({ subListLoading: false });
}
catch (e) {
console.log("==> DB Error");
console.log(e);
await this.setState({ subListLoading: false });
};
};
...
_subjectItems = (value) => {
console.log(value.desc);
let lnum = (typeof value["num"] !== 'undefined') ? value["num"].toString() : null;
let desc = value["desc"].toString();
let lastName = (typeof value["users"][0] !== 'undefined') ? value["users"][0]["lastname"].toString() : null;
let ltype = value["type"].toString();
return (
<DataTable.Row onPress={() => {
this.props.navigation.navigate("UserListScreen", {pnum: lnum, ptype: ltype});
}}>
<DataTable.Cell>
{this._getTypeIcon(ltype)}
</DataTable.Cell>
<DataTable.Cell>
<Text>{desc}</Text>
</DataTable.Cell>
<DataTable.Cell>
<Text>{ lastName }</Text>
</DataTable.Cell>
</DataTable.Row>
);
};
async componentDidMount() {
try {
await this._getSubjects();
}
catch (e) {
console.log("==> componentDidMount error");
console.log(e);
};
};
isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 20;
return layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
};
fetchMore = () => {
};
render() {
let sDimensions = this.scrollDimensions;
return (
<View style={{flex:20, margin:4, flexDirection:"column", justifyContent:"flex-start"}}>
<Title style={{flex:1}}>Lista de Demandas</Title>
<SafeAreaView style={[{flex:19, }, sDimensions]}>
<ScrollView
contentContainerStyle={{}}
onScroll={({nativeEvent}) => {
if (this.isCloseToBottom(nativeEvent)) {
this.fetchMore();
}}}
>
<DataTable>
<DataTable.Header>
<DataTable.Title>Type</DataTable.Title>
<DataTable.Title>Subj</DataTable.Title>
<DataTable.Title>Resp.</DataTable.Title>
</DataTable.Header>
{ !this.state.subListLoading ?
<FlatList
data={this.state.subjectList}
renderItem={({item})=>this._subjectItems(item)}
keyExtractor={item => item.desc}
/>
:
<ActivityIndicator />
}
</DataTable>
</ScrollView>
</SafeAreaView>
</View>
)
}
}
Using Expo 37, React Native paper and AWS Amplify.
As I had such a hard time trying to find which components were not compatible, I simply dropped my full development environment, create a clean one and pulled the latest commit again, checking all components version by version and making sure all of them were at the -g version. The error has stopped after that.
I want to create my own Endomono/Runtastic-like app using RN + expo (This app will be just for me, and I have android phone with pretty decent performance/battery life (Redmi note 7) so I don't worry about performance too much). I wanted to use all-in-one library for that, or just and library that allows me to execute some code each X seconds in background (and getAsyncLocation there). My point is just to send lat/lon data every X seconds to my backend HTTP django-rest-framework powered server.
I just spent whole day trying figure out any way to do that, I tried couple of libraries like this ones: react-native-background-geolocation, react-native-background-timer, react-native-background-job and few more. I followed step by step instalation guide, and I kept getting errors like: null is not an object (evaluating 'RNBackgroundTimer.setTimeout') .
I also tried this: I fixed some errors in this code (imports related), it seemed to work, but when I changed my GPS location using Fake GPS, and only one cast of didFocus functions appears in the console. Here's code:
import React from 'react';
import { EventEmitter } from 'fbemitter';
import { NavigationEvents } from 'react-navigation';
import { AppState, AsyncStorage, Platform, StyleSheet, Text, View, Button } from 'react-native';
import MapView from 'react-native-maps';
import * as Permissions from 'expo-permissions';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import { FontAwesome, MaterialIcons } from '#expo/vector-icons';
const STORAGE_KEY = 'expo-home-locations';
const LOCATION_UPDATES_TASK = 'location-updates';
const locationEventsEmitter = new EventEmitter();
export default class MapScreen extends React.Component {
static navigationOptions = {
title: 'Background location',
};
mapViewRef = React.createRef();
state = {
accuracy: 4,
isTracking: false,
showsBackgroundLocationIndicator: false,
savedLocations: [],
initialRegion: null,
error: null,
};
didFocus = async () => {
console.log("Hello")
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
AppState.addEventListener('change', this.handleAppStateChange);
this.setState({
error:
'Location permissions are required in order to use this feature. You can manually enable them at any time in the "Location Services" section of the Settings app.',
});
return;
} else {
this.setState({ error: null });
}
const { coords } = await Location.getCurrentPositionAsync();
console.log(coords)
const isTracking = await Location.hasStartedLocationUpdatesAsync(LOCATION_UPDATES_TASK);
const task = (await TaskManager.getRegisteredTasksAsync()).find(
({ taskName }) => taskName === LOCATION_UPDATES_TASK
);
const savedLocations = await getSavedLocations();
const accuracy = (task && task.options.accuracy) || this.state.accuracy;
this.eventSubscription = locationEventsEmitter.addListener('update', locations => {
this.setState({ savedLocations: locations });
});
if (!isTracking) {
alert('Click `Start tracking` to start getting location updates.');
}
this.setState({
accuracy,
isTracking,
savedLocations,
initialRegion: {
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.004,
longitudeDelta: 0.002,
},
});
};
handleAppStateChange = nextAppState => {
if (nextAppState !== 'active') {
return;
}
if (this.state.initialRegion) {
AppState.removeEventListener('change', this.handleAppStateChange);
return;
}
this.didFocus();
};
componentWillUnmount() {
if (this.eventSubscription) {
this.eventSubscription.remove();
}
AppState.removeEventListener('change', this.handleAppStateChange);
}
async startLocationUpdates(accuracy = this.state.accuracy) {
await Location.startLocationUpdatesAsync(LOCATION_UPDATES_TASK, {
accuracy,
showsBackgroundLocationIndicator: this.state.showsBackgroundLocationIndicator,
});
if (!this.state.isTracking) {
alert(
'Now you can send app to the background, go somewhere and come back here! You can even terminate the app and it will be woken up when the new significant location change comes out.'
);
}
this.setState({ isTracking: true });
}
async stopLocationUpdates() {
await Location.stopLocationUpdatesAsync(LOCATION_UPDATES_TASK);
this.setState({ isTracking: false });
}
clearLocations = async () => {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify([]));
this.setState({ savedLocations: [] });
};
toggleTracking = async () => {
await AsyncStorage.removeItem(STORAGE_KEY);
if (this.state.isTracking) {
await this.stopLocationUpdates();
} else {
await this.startLocationUpdates();
}
this.setState({ savedLocations: [] });
};
onAccuracyChange = () => {
const next = Location.Accuracy[this.state.accuracy + 1];
const accuracy = next ? Location.Accuracy[next] : Location.Accuracy.Lowest;
this.setState({ accuracy });
if (this.state.isTracking) {
// Restart background task with the new accuracy.
this.startLocationUpdates(accuracy);
}
};
toggleLocationIndicator = async () => {
const showsBackgroundLocationIndicator = !this.state.showsBackgroundLocationIndicator;
this.setState({ showsBackgroundLocationIndicator }, async () => {
if (this.state.isTracking) {
await this.startLocationUpdates();
}
});
};
onCenterMap = async () => {
const { coords } = await Location.getCurrentPositionAsync();
const mapView = this.mapViewRef.current;
if (mapView) {
mapView.animateToRegion({
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.004,
longitudeDelta: 0.002,
});
}
};
renderPolyline() {
const { savedLocations } = this.state;
if (savedLocations.length === 0) {
return null;
}
return (
<MapView.Polyline
coordinates={savedLocations}
strokeWidth={3}
strokeColor={"black"}
/>
);
}
render() {
if (this.state.error) {
return <Text style={styles.errorText}>{this.state.error}</Text>;
}
if (!this.state.initialRegion) {
return <NavigationEvents onDidFocus={this.didFocus} />;
}
return (
<View style={styles.screen}>
<MapView
ref={this.mapViewRef}
style={styles.mapView}
initialRegion={this.state.initialRegion}
showsUserLocation>
{this.renderPolyline()}
</MapView>
<View style={styles.buttons} pointerEvents="box-none">
<View style={styles.topButtons}>
<View style={styles.buttonsColumn}>
{Platform.OS === 'android' ? null : (
<Button style={styles.button} onPress={this.toggleLocationIndicator} title="background/indicator">
<Text>{this.state.showsBackgroundLocationIndicator ? 'Hide' : 'Show'}</Text>
<Text> background </Text>
<FontAwesome name="location-arrow" size={20} color="white" />
<Text> indicator</Text>
</Button>
)}
</View>
<View style={styles.buttonsColumn}>
<Button style={styles.button} onPress={this.onCenterMap} title="my location">
<MaterialIcons name="my-location" size={20} color="white" />
</Button>
</View>
</View>
<View style={styles.bottomButtons}>
<Button style={styles.button} onPress={this.clearLocations} title="clear locations">
Clear locations
</Button>
<Button style={styles.button} onPress={this.toggleTracking} title="start-stop tracking">
{this.state.isTracking ? 'Stop tracking' : 'Start tracking'}
</Button>
</View>
</View>
</View>
);
}
}
async function getSavedLocations() {
try {
const item = await AsyncStorage.getItem(STORAGE_KEY);
return item ? JSON.parse(item) : [];
} catch (e) {
return [];
}
}
if (Platform.OS !== 'android') {
TaskManager.defineTask(LOCATION_UPDATES_TASK, async ({ data: { locations } }) => {
if (locations && locations.length > 0) {
const savedLocations = await getSavedLocations();
const newLocations = locations.map(({ coords }) => ({
latitude: coords.latitude,
longitude: coords.longitude,
}));
savedLocations.push(...newLocations);
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(savedLocations));
locationEventsEmitter.emit('update', savedLocations);
}
});
}
const styles = StyleSheet.create({
screen: {
flex: 1,
},
mapView: {
flex: 1,
},
buttons: {
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
padding: 10,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
},
topButtons: {
flexDirection: 'row',
justifyContent: 'space-between',
},
bottomButtons: {
flexDirection: 'column',
alignItems: 'flex-end',
},
buttonsColumn: {
flexDirection: 'column',
alignItems: 'flex-start',
},
button: {
paddingVertical: 5,
paddingHorizontal: 10,
marginVertical: 5,
},
errorText: {
fontSize: 15,
color: 'rgba(0,0,0,0.7)',
margin: 20,
},
});
If you know any way to easily complete my target (of sending simple HTTP GET with location from background of Expo + RN app to my DRF backend) please let me know.
If you're using Expo you can simply use expo-task-manager and expo-location to get background location updates.
Here's a simplified version that I'm using (and it's working for sure on Android) on the App I'm currently developing:
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import axios from 'axios';
const TASK_FETCH_LOCATION = 'TASK_FETCH_LOCATION';
// 1 define the task passing its name and a callback that will be called whenever the location changes
TaskManager.defineTask(TASK_FETCH_LOCATION, async ({ data: { locations }, error }) => {
if (error) {
console.error(error);
return;
}
const [location] = locations;
try {
const url = `https://<your-api-endpoint>`;
await axios.post(url, { location }); // you should use post instead of get to persist data on the backend
} catch (err) {
console.error(err);
}
});
// 2 start the task
Location.startLocationUpdatesAsync(TASK_FETCH_LOCATION, {
accuracy: Location.Accuracy.Highest,
distanceInterval: 1, // minimum change (in meters) betweens updates
deferredUpdatesInterval: 1000, // minimum interval (in milliseconds) between updates
// foregroundService is how you get the task to be updated as often as would be if the app was open
foregroundService: {
notificationTitle: 'Using your location',
notificationBody: 'To turn off, go back to the app and switch something off.',
},
});
// 3 when you're done, stop it
Location.hasStartedLocationUpdatesAsync(TASK_FETCH_LOCATION).then((value) => {
if (value) {
Location.stopLocationUpdatesAsync(TASK_FETCH_LOCATION);
}
});
It doesn't necessarily work with Expo, but if "eject" your project or start with the React Native CLI (via react-native init) then you could use an Android specific React Native "NativeModule" to accomplish your goal. I like using the react-native-location package, which has great support on iOS for background location updates, but on Android there is a bug currently. I put together an example project which has the necessary Android specific code inside a NativeModule you could use to start from:
https://github.com/andersryanc/ReactNative-LocationSample
I have a button at the middle of my screen. onScroll I want the button to scale down to 0 to disappear and then scale back up to 1 to reappear in a new position at the bottom of the screen. I want to be able call setState (which controls the position of the button) in between the scale down and scale up animations. Something like the code below. Any idea of the best way to add a function call in between these two animations? Or an even better way of doing this?
animateScale = () => {
return (
Animated.sequence([
Animated.timing(
this.state.scale,
{
toValue: 0,
duration: 300
}
),
this.setState({ positionBottom: true }),
Animated.timing(
this.state.scale,
{
toValue: 1,
duration: 300
}
)
]).start()
)
}
After more research I found the answer.start() takes a callback function as shown here:
Calling function after Animate.spring has finished
Here was my final solution:
export default class MyAnimatedScreen extends PureComponent {
state = {
scale: new Animated.Value(1),
positionUp: true,
animating: false,
};
animationStep = (toValue, callback) => () =>
Animated.timing(this.state.scale, {
toValue,
duration: 200,
}).start(callback);
beginAnimation = (value) => {
if (this.state.animating) return;
this.setState(
{ animating: true },
this.animationStep(0, () => {
this.setState(
{ positionUp: value, animating: false },
this.animationStep(1)
);
})
);
};
handleScrollAnim = ({ nativeEvent }) => {
const { y } = nativeEvent.contentOffset;
if (y < 10) {
if (!this.state.positionUp) {
this.beginAnimation(true);
}
} else if (this.state.positionUp) {
this.beginAnimation(false);
}
};
render() {
return (
<View>
<Animated.View
style={[
styles.buttonWrapper,
{ transform: [{ scale: this.state.scale }] },
this.state.positionUp
? styles.buttonAlignTop
: styles.buttonAlignBottom,
]}
>
<ButtonCircle />
</Animated.View>
<ScrollView onScroll={this.handleScrollAnim}>
// scroll stuff here
</ScrollView>
</View>
);
}
}
That is correct answer.
Tested on Android react-native#0.63.2
Animated.sequence([
Animated.timing(someParam, {...}),
{
start: cb => {
//Do something
console.log(`I'm wored!!!`)
cb({ finished: true })
}
},
Animated.timing(someOtherParam, {...}),
]).start();