I followed the following tutorial in order to have the user's location for my react native app:
https://reactnativemaster.com/react-native-geolocation-example/
However, nothing is showing apart from the Hello on my iphone.
I think that the problem is that geocode is null but I do not know how to fix it.
Any tips would be helpful :)
Please see my code below if needed:
import React, { Component } from "react";
import {
StyleSheet,
Text,
View,
} from "react-native";
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';
class App extends Component {
state= {
location:null,
geocode:null,
errorMessage:""
}
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({accuracy:Location.Accuracy.Highest});
const { latitude , longitude } = location.coords
this.getGeocodeAsync({latitude, longitude})
this.setState({ location: {latitude, longitude}});
};
getGeocodeAsync= async (location) => {
let geocode = await Location.reverseGeocodeAsync(location)
this.setState({ geocode})
}
render(){
const {location,geocode, errorMessage } = this.state
return (
<View style={styles.overlay}>
<Text>Hello</Text>
<Text style={styles.heading1}>{geocode ? `${geocode[0].city}, ${geocode[0].isoCountryCode}` :""}</Text>
<Text style={styles.heading2}>{geocode ? geocode[0].street :""}</Text>
<Text style={styles.heading3}>{location ? `${location.latitude}, ${location.longitude}` :""}</Text>
<Text style={styles.heading2}>{errorMessage}</Text>
</View>
);
}
}
export default App;
import React, { Component } from "react";
import {
StyleSheet,
Text,
View,
} from "react-native";
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';
class App extends Component {
state= {
location:null,
geocode:null,
errorMessage:""
}
// You have to call this.getLocationAsync()
componentDidMount(){
// this is needed for geocode
Location.setApiKey("Your-API-KEY-Here")
this.getLocationAsync();
}
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({accuracy:Location.Accuracy.Highest});
const { latitude , longitude } = location.coords
this.setState({ location: {latitude, longitude}});
this.getGeocodeAsync({latitude, longitude}).catch(err => {this.setState({errorMessage: err})})
};
getGeocodeAsync= async (location) => {
let geocode = await Location.reverseGeocodeAsync(location)
this.setState({geocode: geocode})
// check console for geocode response
console.log(geocode)
}
render(){
const {location,geocode, errorMessage } = this.state
return (
<View style={styles.overlay}>
<Text>Hello</Text>
<Text style={styles.heading1}>{geocode ? `${geocode[0].city}, ${geocode[0].region}` :""}</Text>
<Text style={styles.heading2}>{geocode ? geocode[0].country :""}</Text>
<Text style={styles.heading3}>{location ? `${location.latitude}, ${location.longitude}` :""}</Text>
<Text style={styles.heading2}>{errorMessage}</Text>
</View>
);
}
}
// Create styles for your headings here
const styles = StyleSheet.create({
heading1:{
fontSize: 24
},
heading2:{
fontSize: 18
},
heading3:{
fontSize: 12
}
})
export default App;
You need to use your google API key for the geocode service so it won't return null and if you run this without it you should setState for error message to display for geocode.
Related
I am developing an app which allows the user to search for books and then display it in the search results. For displaying the results, I am using a FlatList with 3 columns and displaying the book cover and some basic information about the book.
I am storing the results from the API response in state without the comoponent. As more results are added, the memory consumption increases but the data is in JSON format, no images are store in state.
I have tried, using removeClippedSubviews and few other options that allow setting the window size but that has little to no difference on the memory usage.
Am I missing something here or is there a way to optimise this? Sample code is uploaded to this github repo
Here is the code snippet I am using:
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow strict-local
*/
import type { Node } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Platform,
SafeAreaView,
StatusBar,
StyleSheet,
useColorScheme,
View,
} from 'react-native';
import { Button, SearchBar, useTheme } from 'react-native-elements';
import { searchBooks } from './api/GoogleBooksService';
import HttpClient from './network/HttpClient';
import BookCard from './components/BookCard';
const searchParamsInitialState = {
startIndex: 1,
maxResults: 12,
totalItems: null,
};
let debounceTimer;
const debounce = (callback, time) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(callback, time);
};
const isEndOfList = searchParams => {
const { startIndex, maxResults, totalItems } = searchParams;
if (totalItems == null) {
return false;
}
console.log('isEndOfList', totalItems - (startIndex - 1 + maxResults) < 0);
return totalItems - (startIndex - 1 + maxResults) < 0;
};
const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';
const [isLoading, setIsLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [globalSearchResults, setGlobalSearchResults] = useState([]);
const [searchParams, setSearchParams] = useState(searchParamsInitialState);
let searchCancelToken;
let searchCancelTokenSource;
// This ref will be used to track if the search Term has changed when tab is switched
const searchRef = useRef();
const clearSearch = () => {
console.log('Clear everything!');
searchRef.current = null;
setGlobalSearchResults([]);
setSearchParams(searchParamsInitialState);
setIsLoading(false);
searchCancelTokenSource?.cancel();
searchCancelToken = null;
searchCancelTokenSource = null;
};
useEffect(() => {
debounce(async () => {
setIsLoading(true);
await searchGlobal(searchTerm);
setIsLoading(false);
}, 1000);
}, [searchTerm]);
/**
* Search method
*/
const searchGlobal = async text => {
if (!text) {
// Clear everything
clearSearch();
return;
}
setIsLoading(true);
try {
// Use the initial state values if the search term has changed
let params = searchParams;
if (searchRef.current !== searchTerm) {
params = searchParamsInitialState;
}
const { items, totalItems } = await searchBooks(
text,
params.startIndex,
params.maxResults,
searchCancelTokenSource?.token,
);
if (searchRef.current === searchTerm) {
console.log('Search term has not changed. Appending data');
setGlobalSearchResults(prevState => prevState.concat(items));
setSearchParams(prevState => ({
...prevState,
startIndex: prevState.startIndex + prevState.maxResults,
totalItems,
}));
} else {
console.log(
'Search term has changed. Updating data',
searchTerm,
);
if (!searchTerm) {
console.log('!searchTerm', searchTerm);
clearSearch();
return;
}
setGlobalSearchResults(items);
setSearchParams({
...searchParamsInitialState,
startIndex:
searchParamsInitialState.startIndex +
searchParamsInitialState.maxResults,
totalItems,
});
}
searchRef.current = text;
} catch (err) {
if (HttpClient.isCancel(err)) {
console.error('Cancelled', err.message);
}
console.error(`Error searching for "${text}"`, err);
}
setIsLoading(false);
};
const renderGlobalItems = ({ item }) => {
return <BookCard book={item} />;
};
const { theme } = useTheme();
return (
<SafeAreaView style={styles.backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
/>
<View style={styles.container}>
<SearchBar
showLoading={isLoading}
placeholder="Enter search term here"
onChangeText={text => {
setSearchTerm(text);
}}
value={searchTerm}
platform={Platform.OS}
/>
{isLoading && globalSearchResults.length <= 0 && (
<ActivityIndicator animating style={styles.loader} />
)}
{globalSearchResults.length > 0 && (
<FlatList
removeClippedSubviews
columnWrapperStyle={styles.columnWrapper}
data={globalSearchResults}
numColumns={3}
showsHorizontalScrollIndicator={false}
keyExtractor={item => item + item.id}
renderItem={renderGlobalItems}
ListFooterComponent={
<>
{!isLoading &&
!isEndOfList(searchParams) &&
searchParams.totalItems > 0 && (
<Button
type="clear"
title="Load more..."
onPress={async () => {
await searchGlobal(searchTerm);
}}
/>
)}
{isLoading && searchParams.totalItems != null && (
<ActivityIndicator
size="large"
style={{
justifyContent: 'center',
}}
color={theme.colors.primary}
/>
)}
</>
}
/>
)}
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
backgroundStyle: 'white',
container: {
height: '100%',
width: '100%',
},
columnWrapper: {
flex: 1,
},
loader: {
flex: 1,
justifyContent: 'center',
},
});
export default App;
There is something called PureComponent in react native. If you create FlatList as PureComponent, you can see lot of improvement.
It will not rerender items until data has been changed.
for example:
class MyList extends React.PureComponent {
}
For more reference check this
Can you try to chuck your array of list items into small sub-arrays, this package uses this mechanism https://github.com/bolan9999/react-native-largelist
The package has been praised by complex app teams including the Discord Mobile Team - https://discord.com/blog/how-discord-achieves-native-ios-performance-with-react-native
My App screen. js returns
[Unhandled promise rejection: Error: Element type is invalid: expected
a string (for built-in components) or a class/function (for composite
components) but got: undefined. You likely forgot to export your
component from the file it's defined in, or you might have mixed up
default and named imports.]
The error does not give me the location of the problem other than the 'render method' can you see where the error is, could you help me find the source of the problem?
Thank you very much
import Splash from './src/components/Splash';
import React from "react";
import {
View,
Text,
StatusBar,
Image,
ActivityIndicator,
} from "react-native";
import MainStackNavigator from "./Navigation/StackNavigator";
import styles from "./assets/styles";
import {
retrieveAppLang,
userSessionActive
} from "./src/common/Preferences";
import i18n from "./src/i18n";
export default class App extends React.Component {
constructor(props) {
super(props);
Text.defaultProps = Text.defaultProps || {};
Text.defaultProps.allowFontScaling = false;
this.state = {
isFirstConnection: true,
status: 0,
};
}
async UNSAFE_componentWillMount() {
let lang = await retrieveAppLang();
let isConnected = await userSessionActive();
if (lang.length == 2) {
i18n.changeLanguage(lang);
}
if (isConnected === true && this.props && this.props.navigation) {
this.props.navigation.navigate("BottomTabNavigator");
}
}
async componentDidMount() {
const data = await this.performTimeConsumingTask();
if (data !== null) {
this.setState({
isFirstConnection: false,
status: 1,
});
}
}
performTimeConsumingTask = async () => {
return new Promise((resolve) =>
setTimeout(() => {
resolve("result");
}, 750)
);
};
render() {
if (this.state.status == 1) {
if (this.state.isFirstConnection) {
return <Splash />;
} else {
return <MainStackNavigator screenProps={'Authentication'} />;
}
}
return (
<View style={[styles.container, styles.containerCentered]}>
<StatusBar hidden={true} />
<View style={styles.subContainer}>
<Image
style={styles.logo}
source={require("./assets/images/logo.png")}
/>
<ActivityIndicator size="large" color="#43300E" />
<Text>{i18n.t("app.loading") + "..."}</Text>
</View>
</View>
);
}
}
I have a class component and I need to pass one of its function to the drawer and use it, but don't know if it's possible:
class Edit_note extends Component {
save_changes = async() => {
let clear_content = this.state.content.replace(/ /g,""); //replace al
try {
const data = JSON.parse(await AsyncStorage.getItem("data"));
const index_to_find = this.array_notes.findIndex(obj => obj.note_number === this.note[0].note_number);
const edited_note = this.note.map((note) => {
note.content = clear_content;
return {...note}
});
this.array_notes.splice(index_to_find, 1, edited_note[0]);
data.array_notes = this.array_notes;
await AsyncStorage.setItem("data", JSON.stringify(data));
} catch(error) {
alert(error);
}
}
render() {
return (
<>
<Text>hi</Text>
</>
);
}
}
export default Edit_note;
this is my class component, and I want to export the function save_changes and use it in the header of edit note, put it in the icon
import React, { Component } from "react";
import { Text, View } from "react-native";
import { Feather } from '#expo/vector-icons';
class Header extends Component {
render() {
return (
<>
<View style ={{backgroundColor: "white", flexDirection: "row", alignItems: "center"}}>
<View>
<Text style = {{color: "black", fontSize: 30, marginLeft: -20}}>{this.props.title}</Text>
</View>
<Feather name="check-square" size={24} color = "black" />
</View>
</>
);
}
}
export default Header;
How can I do that?
You can not do that. Only way is to extract save_changes out of the class as a utility, export it and use it where ever u need passing the right parameters.
In the file where you have your class component, make the following changes:
export const save_changes = async(<Pass the parameter here>) => {
let clear_content = this.state.content.replace(/ /g,""); //replace al
try {
const data = JSON.parse(await AsyncStorage.getItem("data"));
const index_to_find = this.array_notes.findIndex(obj => obj.note_number === this.note[0].note_number);
const edited_note = this.note.map((note) => {
note.content = clear_content;
return {...note}
});
this.array_notes.splice(index_to_find, 1, edited_note[0]);
data.array_notes = this.array_notes;
await AsyncStorage.setItem("data", JSON.stringify(data));
}catch(error) {
alert(error);
}
}
class Edit_note extends Component {
render() {
return (
<>
<Text>hi</Text>
</>
);
}
}
export default Edit_note;
Now you can import the save_changes function anywhere from this file
To export a function from your component, you need it to be public static, and remove all usage of this within the function. The public word means that the function can be accessed from outside the component, with Edit_note.save_changes(). The keyword static is used to make the function independant of the component's instance.
To import the data you need from your instance, you can pass them as parameters in your function save_changes.
As a result, you should have something like this:
public static async save_changes (content, array_notes, note) {
let clear_content = content.replace(/ /g,""); //replace al
try {
const data = JSON.parse(await AsyncStorage.getItem("data"));
const index_to_find = array_notes.findIndex(obj => obj.note_number === note[0].note_number);
const edited_note = note.map((note) => {
note.content = clear_content;
return {...note}
});
array_notes.splice(index_to_find, 1, edited_note[0]);
data.array_notes = array_notes;
await AsyncStorage.setItem("data", JSON.stringify(data));
} catch(error) {
alert(error);
}
}
And then, you can call this function in any other component with Edit_note.save_changes(), as long as the component Edit-note is imported as well at the top lines of the component.
I am successfully able to receive push notifications in my react native app but I am unable to make a redirect based on them. What happens is that on android, when I touch the notification, the app completely reloads and the notification data is lost. How can I handle this?
This is my code
import React from 'react';
import { AppState, View, StatusBar } from 'react-native';
import { ScreenHost } from './src/app/screen-host';
import { AppHost } from './src/debug/app-host';
import { settings } from './src/constants/settings';
import { services } from './src/services/services';
import Expo from 'expo';
async function getToken() {
// Remote notifications do not work in simulators, only on device
if (!Expo.Constants.isDevice) {
return;
}
let { status } = await Expo.Permissions.askAsync(
Expo.Permissions.NOTIFICATIONS,
);
if (status !== 'granted') {
return;
}
let value = await Expo.Notifications.getExpoPushTokenAsync();
console.log('Our token', value);
}
export default class App extends React.Component {
state = {
appState: AppState.currentState,
conv_id: ''
}
componentDidMount() {
getToken();
AppState.addEventListener('change', this._handleAppStateChange);
//Expo.Notifications.setBadgeNumberAsync(0);
}
componentWillUnmount() {
this.listener && this.listener.remove();
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if(this.state.conv_id == ''){
this.listener = Expo.Notifications.addListener(this.handleNotification);
}
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
console.log('App has come to the foreground!');
if(this.state.conv_id != ''){
services.store.gotoConversation(this.state.conv_id);
}
}
Expo.Notifications.setBadgeNumberAsync(0);
this.setState({appState: nextAppState});
}
handleNotification = ({ origin, data }) => {
console.log(origin);
this.state.conv_id = data.conv_id;
};
render() {
if (settings.useDebugLayout) {
return (
<AppHost>
<AppMain />
</AppHost>
);
} else {
return (
<AppMain />
);
}
}
}
class AppMain extends React.Component {
render() {
return (
<View style={{ flex: 1 }}>
<StatusBar barStyle="light-content" />
<ScreenHost data={services.store._showNav}/>
</View>
);
}
}
I want to call my services.store.gotoConversation(this.state.conv_id); but the screen loads the splash screen and the notification data is lost.
I went through
https://docs.expo.io/versions/latest/guides/push-notifications
But i can't seem to figure what is the main issue. Please help me. I will award 50 Bounty to who so ever helps me with my issue. Thankyou.
I'm using redux for the first time and something subtle is getting by me.
I have a container called Dashboard that displays two SimpleTabs. A simple tab is component that gets pressed and returns a number to its container for the item pressed. I can see actions being dispatched, event handler firing etc but the state being received in mapStateToProps never contains the item values. This might be why the render is never getting fired because the state is not changed.
Note: I've used the Ignite boilerplate as a starting point. It makes use of reduxsauce so the DashboardRedux.js may look a little unusual.
Dashboard.js
import React, { Component } from 'react'
import { ScrollView, Text, View, Image, TouchableOpacity, StyleSheet } from 'react-native'
import moment from 'moment'
import { Images, Colors, Metrics, ApplicationStyles } from '../Themes'
import SimpleTab from '../Components/SimpleTab'
import DashboardHeader from '../Components/DashboardHeader'
import DashboardActions from '../Redux/DashboardRedux'
import { connect } from 'react-redux'
export class Dashboard extends Component {
//TODO make numbers into enums
constructor(props) {
super(props)
this.updateTimeframe = this.updateTimeframe.bind(this)
this.updateAnalysisView = this.updateAnalysisView.bind(this)
const curTimeframe = 0
const curAnalysisView = 0
this.state = {curTimeframe, curAnalysisView}
}
// Event handler for timeframe tab
updateTimeframe(newValue) {
//newValue gets received as expected
this.props.updateTimeframe(newValue)
}
// Event handler for analysisview tab
updateAnalysisView(newValue) {
this.props.updateAnalysisView(newValue)
}
getUpdateTime = () => {
let s = moment().format("h:mm a")
return s
}
// Takes us back to login
openLoginScreen = () => {
//TODO does navigater have notion of <back>?
this.props.navigation.navigate('LoginScreen')
}
// For info on flex: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
render () {
let styles = ApplicationStyles.screen
/*
let localStyles = StyleSheet.create({
container: {
paddingBottom: Metrics.baseMargin
},
centered: {
alignItems: 'center'
}
})
console.log(styles)
*/
return (
//Problem: this.props.curTimeframe is always undefined
<View style={styles.mainContainer}>
<DashboardHeader updateTime={this.getUpdateTime()}></DashboardHeader>
<View style={{justifyContent: 'space-between'}} >
<SimpleTab
onSelect={this.updateTimeframe}
curTab={this.props.curTimeframe}
tabNames={["TODAY", "1W", "1M", "3M", "6M"]}
/>
</View>
<View style={{flex:1}} >
<Text style={{color: Colors.snow}}>
Analytical stuff for {this.props.curTimeframe} and {this.props.curAnalysisView}
</Text>
</View>
<View style={{height:60, justifyContent: 'space-between'}} >
<SimpleTab
onSelect={this.updateAnalysisView}
curTab={this.props.curAnalysisView}
tabNames={["HOME", "DAYPART", "REC", "INGRED", "SETTINGS"]}
/>
</View>
</View>
)}
}
const mapStateToProps = (state) => {
// Problem: state passed never contains curAnalysisView or curTimeframe
return {
curAnalysisView: state.curAnalysisView,
curTimeframe: state.curTimeframe
}
}
const mapDispatchToProps = (dispatch) => {
return {
updateTimeframe: newValue => dispatch(DashboardActions.updateTimeframe(newValue)),
updateAnalysisView: newValue => dispatch(DashboardActions.updateAnalysisView(newValue))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
DashboardRedux.js
import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
updateTimeframe: ['newValue'],
updateAnalysisView: ['newValue'],
})
export default Creators
export const DashboardTypes = Types
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
curTimeframe: 0,
curAnalysisView: 0
})
/* ------------- Reducers ------------- */
export const updateTimeframe = (state, {newValue}) => {
//newValue gets passed as expected
return state.merge({curTimeframe: newValue});
}
export const updateAnalysisView = (state, {newValue}) => {
return state.merge({curAnalysisView: newValue});
}
/* ------------- Hookup Reducers To Types ------------- */
export const reducer = createReducer(INITIAL_STATE, {
[Types.UPDATE_TIMEFRAME]: updateTimeframe,
[Types.UPDATE_ANALYSIS_VIEW]: updateAnalysisView
})
SimpleTab.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { View, Text, Image, StyleSheet, TouchableHighlight } from 'react-native'
import { Colors, Metrics, Fonts, Images } from '../Themes/'
import styles from '../Themes/ApplicationStyles'
export default class SimpleTab extends Component {
static defaultProps = {
onSelect: null,
curTab: 0,
tabNames: ["Tab1", "Tab2", "Tab3"]
}
static propTypes = {
onSelect: PropTypes.func,
curTab: PropTypes.number,
tabNames: PropTypes.array
}
tabSelected = (tabNum) => {
this.props.onSelect(tabNum);
}
renderTabBar = () => {
let localStyles = StyleSheet.create({
unselectedText: {
marginTop: Metrics.baseMargin,
marginHorizontal: Metrics.baseMargin,
textAlign: 'center',
fontFamily: Fonts.type.base,
fontSize: Fonts.size.regular,
color: Colors.snow
},
selectedText: {
marginTop: Metrics.baseMargin,
marginHorizontal: Metrics.baseMargin,
textAlign: 'center',
fontFamily: Fonts.type.base,
fontSize: Fonts.size.regular,
fontWeight: 'bold',
color: Colors.fire
}
})
let result = []
for (i=0; i<this.props.tabNames.length; i++) {
let tabStyle = (i == this.props.curTab) ? localStyles.selectedText : localStyles.unselectedText
result.push(
<TouchableHighlight key={this.props.tabNames[i]} onPress={this.tabSelected.bind(this, i)}>
<Text style={tabStyle}>{this.props.tabNames[i]}</Text>
</TouchableHighlight>
)
}
return result
}
render () {
console.log("rendering tab")
return (
<View flexDirection='row' style={styles.contentContainer}>
{this.renderTabBar()}
</View>
)
}
}
MapStateToProps receives the new state properties via reducers. Your MapStatetoProps in Dashboard.js should look like below to get the new values.
const mapStateToProps = (state) => {
// Problem: state passed never contains curAnalysisView or curTimeframe
//new state values should be accessed via reducers..
return {
curAnalysisView: state.updateAnalysisView['curAnalysisView'],
curTimeframe: state.updateTimeframe['curTimeframe']
}
}
the mapStateToProps should like:
const mapStateToProps = (state) => {
const stateObj = state.toJS()
return {
curAnalysisView: stateObj.curAnalysisView,
curTimeframe: stateObj.curTimeframe
}
}
the .toJS() function converts it from immutable object to JS object.
Also, the reducer should have a default case that just returns the current state for when there is no action passed.
It turned out I needed to specify the "dashboard" branch of the state tree like this:
const mapStateToProps = (state) => {
return {
curAnalysisView: state.dashboard.curAnalysisView,
curTimeframe: state.dashboard.curTimeframe
}
}