Google service account authentication from react native - android

I'd like to use service account authentication within my react native app, in order to let people update a spreadsheet without the need for them to log into their google account.
I'm pretty lost on how to do it, since all frontend code I see is related to traditional oauth2 login.
Any idea?

I finally dropped the idea of service account and used client oauth.
I relied on the following React Native library : https://github.com/joonhocho/react-native-google-sign-in
Here is my authentication service (simplified, redux + redux thunk based):
// <project_folder>/src/services/auth.js
import GoogleSignIn from 'react-native-google-sign-in';
import { store } from '../store';
import auth from '../actions/auth';
export default () => {
GoogleSignIn.configure({
// https://developers.google.com/identity/protocols/googlescopes
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
clientID: 'XXXXXXXXXXXXXXXXX.apps.googleusercontent.com',
}).then(() => {
GoogleSignIn.signInPromise().then((user) => {
store.dispatch(auth(user.accessToken));
}).catch((err) => {
console.log(err);
});
}).catch((err) => {
console.log(err);
});
};
So then I have the user.accessToken in my redux store and can use it elsewhere to create REST requests to the google sheets API.
Here is a simple example of a component that handles the auth and then retrieves some data from the sheet:
// <project_folder>/src/main.js
import React, { Component } from 'react';
import {
ActivityIndicator,
Text,
View,
StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
import auth from './services/auth';
import Sheet from './services/sheet';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
const sheetId = 'XX-XXXXXX_XXX_XXXXXXXXXXXXXXXXXXXXXX';
class Main extends Component {
constructor(props) {
super(props);
this.state = {
animating: true,
};
}
componentDidMount() {
auth();
}
componentWillUpdate(nextProps) {
if (this.props.token !== nextProps.token) this.setState({ animating: false });
}
componentDidUpdate(nextProps) {
this.sheet = new Sheet(id, this.props.token);
this.sheet.getDoc();
}
render() {
return (
<View style={styles.container}>
<ActivityIndicator
animating={this.state.animating}
style={{ height: 80 }}
size="large"
/>
{!this.state.animating &&
<Text>
{this.props.name}
</Text>
}
</View>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.auth.get('token'),
name: state.sheet.get('name'),
};
};
export default connect(mapStateToProps)(Main);
And here is a basic service to read/write a sheet:
// <project_folder>/services/sheet.js
import { store } from '../store';
import {
nameGet,
valueGet,
valuePost,
}
from '../actions/sheet';
class Sheet {
constructor(id, token) {
this.id = id;
this.token = token;
this.endPoint = `https://sheets.googleapis.com/v4/spreadsheets/${this.id}`;
}
getDoc() {
fetch(`${this.endPoint}?access_token=${this.token}`).then((response) => {
response.json().then((data) => {
console.log(data);
store.dispatch(nameGet(data.properties.title));
});
});
}
getCell(sheet, cell) {
const range = `${sheet}!${cell}`;
fetch(`${this.endPoint}/values/${range}?access_token=${this.token}`)
.then((response) => {
response.json()
.then((data) => {
console.log(data);
store.dispatch(valueGet(data.values[0][0]));
})
.catch((err) => {
console.log(err);
});
})
.catch((err) => {
console.log(err);
});
}
writeCell(sheet, cell, value) {
const range = `${sheet}!${cell}`;
const body = JSON.stringify({ values: [[value]] });
fetch(
`${this.endPoint}/values/${range}?valueInputOption=USER_ENTERED&access_token=${this.token}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body,
}
)
.then((response) => {
response.json()
.then((data) => {
console.log(data);
store.dispatch(valuePost('OK'));
})
.catch((err) => {
console.log(err);
});
})
.catch((err) => {
console.log(err);
});
}
}
export default Sheet;
If there is a better way to do, please let me know.

I have no fully idea about this but yes if you can set the sheet permission as public and access from your application it will not ask you authentication.
also you may use below link may help more technical Google Sheets API v4

Related

How to grant camera permission in react native expo app (android)

My code is :
import React, { useEffect } from "react";
import * as ImagePicker from "expo-image-picker";
import Screen from "./app/components/Screen";
export default function App() {
async function permisionFunction() {
const result = await ImagePicker.getCameraPermissionsAsync();
if (!result.granted) {
console.log(result);
alert("need access to gallery for this app to work");
}
}
useEffect(() => {
permisionFunction();
}, []);
return <Screen></Screen>;
}
I denied the permissions for camera when it was promted for the first time.
Now whenever I opens the app always need access to gallery for this app to work this message is shown up. for android.
I tried by giving all the permissions to the expo app in settings but still same msg appears.
How can I solve this.
Use expo-cameramodule to access device camera.
I have curated the working example of small app with which you can access pictures from gallery as well as device camera.
Working App: Expo Snack
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Image } from 'react-native';
import { Camera } from 'expo-camera';
import * as ImagePicker from 'expo-image-picker';
export default function Add({ navigation }) {
const [cameraPermission, setCameraPermission] = useState(null);
const [galleryPermission, setGalleryPermission] = useState(null);
const [camera, setCamera] = useState(null);
const [imageUri, setImageUri] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.back);
const permisionFunction = async () => {
// here is how you can get the camera permission
const cameraPermission = await Camera.requestPermissionsAsync();
setCameraPermission(cameraPermission.status === 'granted');
const imagePermission = await ImagePicker.getMediaLibraryPermissionsAsync();
console.log(imagePermission.status);
setGalleryPermission(imagePermission.status === 'granted');
if (
imagePermission.status !== 'granted' &&
cameraPermission.status !== 'granted'
) {
alert('Permission for media access needed.');
}
};
useEffect(() => {
permisionFunction();
}, []);
const takePicture = async () => {
if (camera) {
const data = await camera.takePictureAsync(null);
console.log(data.uri);
setImageUri(data.uri);
}
};
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
console.log(result);
if (!result.cancelled) {
setImageUri(result.uri);
}
};
return (
<View style={styles.container}>
<View style={styles.cameraContainer}>
<Camera
ref={(ref) => setCamera(ref)}
style={styles.fixedRatio}
type={type}
ratio={'1:1'}
/>
</View>
<Button title={'Take Picture'} onPress={takePicture} />
<Button title={'Gallery'} onPress={pickImage} />
{imageUri && <Image source={{ uri: imageUri }} style={{ flex: 1 }} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
cameraContainer: {
flex: 1,
flexDirection: 'row',
},
fixedRatio: {
flex: 1,
aspectRatio: 1,
},
button: {
flex: 0.1,
padding: 10,
alignSelf: 'flex-end',
alignItems: 'center',
},
});
It's mention in doc Configuration.
In managed apps, Camera requires Permissions.CAMERA. Video recording requires Permissions.AUDIO_RECORDING.
I have the same problem... I'm using expo SDK 42 and after a denied answer the permissions request does not trigger again anymore.
Anyway, You have some mistakes in your code, you have to watch the status property of the permissions response.
this is how to check the response status
const requestPermissions = async () => {
try {
const {
status,
} = await ImagePicker.requestMediaLibraryPermissionsAsync();
console.log('status lib', status);
setHasPickerPermission(status === 'granted');
} catch (error) {
console.log('error', error);
}
try {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
console.log('status camera', status);
setHasCameraPermission(status === 'granted');
} catch (error) {
console.log('error', error);
}
};
useEffect(() => {
requestPermissions();
}, []);
If the user has denied the camera permission the first time, the second time you request permission with requestCameraPermissionsAsync() the answer will always be "denied" unless the user manually changes the permission in the app settings.
import { Linking } from "react-native";
import { useCameraPermissions } from "expo-image-picker";
export default function App() {
const [status, requestPermissions] = useCameraPermissions();
const requestPermissionAgain = () => {
Linking.openSettings();
}
useEffect(() => {
if (!status?.granted) requestPermissions();
}, []);
}

Unable to move next screen in React navigation V5 Using Firebase Auth

Im using React navigation version 5 with Firebase integration. Using that I'm trying to make authentication flow. I almost done a Signing flow and it works Perfect, but after Firebase Signed in it will not render and it is in still same SignIn Page in mobile. PFB the Code of My AppRouter Page.
import React, { Component, useEffect } from 'react'
import { View, Platform, TouchableOpacity, Text, Image, Dimensions, Slider, Button, ActivityIndicator, Alert } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
import RootStackScreen from '../Router/RootStackScreen'
import HomeStackScreen from './TabScreens'
import AsyncStorage from '#react-native-community/async-storage';
import { Authcontext } from '../Components/context'
import auth from '#react-native-firebase/auth';
import Home from '../Pages/OnboardingScreen/Home';
var { height, width } = Dimensions.get('window')
const Drawer = createDrawerNavigator();
const AppRouter = ({navigation}) => {
const initialoginState = {
isLoading: true,
email: null,
userToken: null
};
const loginReducer = (prevState, action) => {
switch (action.type) {
case 'RETRIVE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'LOGIN':
return {
...prevState,
email: action.id,
userToken: action.token,
isLoading: false,
};
case 'LOGOUT':
return {
...prevState,
email: null,
userToken: null,
isLoading: false,
};
case 'REGISTER':
return {
...prevState,
email: action.id,
userToken: action.token,
isLoading: false,
};
}
}
const [loginState, dispatch] = React.useReducer(loginReducer, initialoginState)
const authContext = React.useMemo(() => ({
signIn: async (email, password) => {
let userToken;
userToken = null;
if (email !== '' && password !== '') {
auth().signInWithEmailAndPassword(email, password)
.then(async (success) => {
try {
await AsyncStorage.setItem('userToken', success.user.uid)
} catch (e) {
console.log(e)
Alert.alert('Shopping', e)
}
})
.catch(error => {
if (error.code === 'auth/email-already-in-use') {
Alert.alert('Shopping', 'That email address is already in use!')
}
if (error.code === 'auth/invalid-email') {
Alert.alert('Shopping', 'That email address is invalid!')
}
Alert.alert('Shopping', error.code)
});
} else {
Alert.alert('Shopping', 'Invalid Email / Password')
}
dispatch({ type: 'LOGIN', id: email, token: userToken , isLoading: false})
},
signUp: () => {
//Pending
},
signOut: async () => {
try {
await AsyncStorage.removeItem('userToken')
} catch (e) {
console.log(e)
}
dispatch({ type: 'LOGOUT' })
},
}), [])
useEffect(() => {
setTimeout(async () => {
let userToken;
userToken = null;
try {
userToken = await AsyncStorage.getItem('userToken')
console.log('token', userToken)
} catch (e) {
console.log(e)
}
dispatch({ type: 'RETRIVE_TOKEN', token: userToken })
}, 1000)
}, []);
if (loginState.isLoading) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator size="large" color="black" />
</View>
)
}
return (
<Authcontext.Provider value={authContext}>
<NavigationContainer>
{loginState.userToken !== null ?
(
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeStackScreen} /> //Dashboard Screens
</Drawer.Navigator>
) :
<RootStackScreen /> //Authentication Screens
}
</NavigationContainer>
</Authcontext.Provider>
)
}
export default AppRouter
Thanks in Advance
You need to dispatch login inner signInWithEmailAndPassword then or userToken always will be null because of the async.
const authContext = React.useMemo(() => ({
signIn: async (email, password) => {
let userToken;
userToken = null;
if (email !== '' && password !== '') {
auth().signInWithEmailAndPassword(email, password)
.then(async (success) => {
dispatch({ type: 'LOGIN', id: email, token: userToken , isLoading: false})
try {
await AsyncStorage.setItem('userToken', success.user.uid)
} catch (e) {
console.log(e)
Alert.alert('Shopping', e)
}
})
.catch(error => {
if (error.code === 'auth/email-already-in-use') {
Alert.alert('Shopping', 'That email address is already in use!')
}
if (error.code === 'auth/invalid-email') {
Alert.alert('Shopping', 'That email address is invalid!')
}
Alert.alert('Shopping', error.code)
});
} else {
Alert.alert('Shopping', 'Invalid Email / Password')
}
},
signUp: () => {
//Pending
},
signOut: async () => {
try {
await AsyncStorage.removeItem('userToken')
} catch (e) {
console.log(e)
}
dispatch({ type: 'LOGOUT' })
},
}), [])

Global in-app notifications

I wish to utilize an in-app notification system, aka a more attractive and less in your face' use of alerts to let the user know what actions are being done, especially when for instance a barcode has been detected but it needs to send that barcode to the server and the user needs to wait.
I have found this lib and have attempted to implement it; but as I am using React Navigation and I wish to render the item at the very top of the application, it gets cut off by React Native header
Is it possible to have a function I can create and reference whenever I want a global notification and it will render on the very top I would imagine it would need to render here:
import React from 'react';
import { createBottomTabNavigator,createStackNavigator } from 'react-navigation';
import SearchTab from './components/Tabs/SearchTab';
import HomeTab from './components/Tabs/HomeTab';
import ScannerTab from './components/Tabs/ScannerTab';
import SettingsTab from './components/Tabs/SettingsTab';
import Ionicons from 'react-native-vector-icons/Ionicons';
import StockModal from './components/Modals/StockModal';
const MainStack = createBottomTabNavigator(
{
Home: HomeTab,
Search: SearchTab,
Scanner: ScannerTab,
Settings: SettingsTab,
//Todo: Total overlay modals HERE
},
{
navigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, tintColor }) => {
const { routeName } = navigation.state;
let iconName;
if (routeName === 'Home') {
iconName = `ios-information-circle${focused ? '' : '-outline'}`;
} else if (routeName === 'Settings') {
iconName = `ios-options${focused ? '' : '-outline'}`;
}else if (routeName === 'Scanner') {
iconName = `ios-barcode${focused ? '' : '-outline'}`;
}else if (routeName === 'Search') {
iconName = `ios-search${focused ? '' : '-outline'}`;
}
return <Ionicons name={iconName} size={25} color={tintColor} />;
},
}),
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
},
}
);
export default RootStack = createStackNavigator(
{
Main: {
screen: MainStack,
},
QuickStockScreen: {
screen: StockModal,
},
},
{
mode: 'modal',
headerMode: 'none',
}
);
But even if that's possible, I am not sure how its possible to build a function that tells the notification to show; React Redux comes to mind but I don't wish to implement such a cumbersome system just for one feature and it was something I considered when creating his application and decided against.
The notification system in question (not very clear documentation or examples sadly) https://www.npmjs.com/package/react-native-in-app-notification
Here is the navigation lib I am using: https://reactnavigation.org/
What you want would be a component that is a the same level of the navigation (So it can display over it). In multiple projects, I use react-native-root-siblings to do so. It allows you to add UI over the app and so over the navigation.
An exemple how what I made with it. The dark layer and the box at the bottom are part of the Siblings Component.
https://gyazo.com/7ad3fc3fea767ea84243aaa493294670
The Siblings is used like the Alert of React-Native, so as a function (which is quite useful!)
messageMenu.js
import React, { Component } from 'react';
import RootSiblings from 'react-native-root-siblings';
import MessageMenuContainer from './MessageMenuContainer';
export default class Dialog extends Component {
static show = (props) => new RootSiblings(<MessageMenuContainer {...props} />);
static update = (menu, props) => {
if (menu instanceof RootSiblings) {
menu.update(<MessageMenuContainer {...props} />);
} else {
console.warn(`Dialog.update expected a \`RootSiblings\` instance as argument.\nBut got \`${typeof menu}\` instead.`);
}
}
static close = (menu) => {
if (menu instanceof RootSiblings) {
menu.destroy();
} else {
console.warn(`Dialog.destroy expected a \`RootSiblings\` instance as argument.\nBut got \`${typeof menu}\` instead.`);
}
}
render() {
return null;
}
}
export {
RootSiblings as Manager,
};
Where the MessageMenuContainer is your component to render at the top.
Component using the Root Siblings:
import React from 'react';
import PropTypes from 'prop-types';
import I18n from 'react-native-i18n';
import { BackHandler, Keyboard, Platform, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import DraftMenu from './messageMenu'; //HERE IS THE IMPORT YOU WANT
import { Metrics, Colors, Fonts } from '../../main/themes';
class DraftBackButton extends React.Component {
state = {
draftMenu: undefined,
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackAndroid);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackAndroid);
}
handleBackAndroid = () => {
this.handleBack();
return true;
}
handleBack = async () => {
Keyboard.dismiss();
await this.openDraftMenu();
}
openDraftMenu = async () => {
if (this.state.draftMenu) {
await DraftMenu.update(this.state.draftMenu, this.draftMenuProps());
} else {
const draftMenu = await DraftMenu.show(this.draftMenuProps());
this.setState({ draftMenu: draftMenu });
}
}
draftMenuProps = () => ({
options: [
{ title: I18n.t('message.deleteDraft'), onPress: this.deleteDraft, icon: 'trash' },
{ title: I18n.t('message.saveDraft'), onPress: this.saveOrUpdateDraft, icon: 'documents' },
{ title: I18n.t('cancel'), icon: 'close', style: { backgroundColor: Colors.tertiaryBackground } },
],
destroyMenuComponent: async () => {
DraftMenu.close(this.state.draftMenu);
await this.setState({ draftMenu: undefined });
},
withIcon: true,
})
saveOrUpdateDraft = async () => {
// SAVE OR UPDATE DRAFT. NOT IMPORTANT
}
saveDraft = async () => {
// SAVING THE DRAFT
}
updateDraft = async () => {
// UPDATING THE DRAFT
}
deleteDraft = async () => {
// DELETING THE DRAFT
}
render() {
return (
<TouchableOpacity
hitSlop={Metrics.touchable.largeHitSlop}
onPress={() => {
this.handleBack();
}}
>
<Text>BUTTON</Text>
</TouchableOpacity>
);
}
}
DraftBackButton.propTypes = {
// ALL THE PROPTYPES
};
function mapStateToProps(state, ownProps) {
//
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ fetchMessages }, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DraftBackButton);
The best thing with this lib is that you can call the .show anywhere in your app and it will render at the very top!
Hope it's what you're looking for!
EDIT:
I updated the example of how to use the Root Siblings.
Here's the content of my MessageContainer which will be display on top of everything
import React from 'react';
import PropTypes from 'prop-types';
import { Animated, Dimensions, InteractionManager, StyleSheet, TouchableOpacity, View } from 'react-native';
import MessageMenuItem from './MessageMenuItem';
import { Colors } from '../../../main/themes';
const { width, height } = Dimensions.get('window');
const OPTION_HEIGHT = 55;
const OVERLAY_OPACITY = 0.5;
export default class DraftMenuContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
animatedHeight: new Animated.Value(0),
animatedOpacity: new Animated.Value(0),
menuHeight: props.options.length * OPTION_HEIGHT,
};
}
componentDidMount() {
this.onOpen();
}
// Using Animated from react-native to make the animation (fade in/out of the dark layer and the dimensions of the actual content)
onOpen = async () => {
await this.state.animatedHeight.setValue(0);
await this.state.animatedOpacity.setValue(0);
Animated.parallel([
Animated.timing(this.state.animatedHeight, { toValue: this.state.menuHeight, duration: 200 }),
Animated.timing(this.state.animatedOpacity, { toValue: OVERLAY_OPACITY, duration: 200 }),
]).start();
}
onClose = async () => {
await this.state.animatedHeight.setValue(this.state.menuHeight);
await this.state.animatedOpacity.setValue(OVERLAY_OPACITY);
Animated.parallel([
Animated.timing(this.state.animatedHeight, { toValue: 0, duration: 200 }),
Animated.timing(this.state.animatedOpacity, { toValue: 0, duration: 200 }),
]).start(() => this.props.destroyMenuComponent()); // HERE IS IMPORTANT. Once you're done with the component, you need to destroy it. To do so, you need to set a props 'destroyMenuComponent' which is set at the creation of the initial view. See the other code what it actually do
}
render() {
return (
<View style={styles.menu}>
<Animated.View style={[styles.backgroundOverlay, { opacity: this.state.animatedOpacity }]}>
<TouchableOpacity
activeOpacity={1}
onPress={() => this.onClose()}
style={{ flex: 1 }}
/>
</Animated.View>
<Animated.View style={[styles.container, { height: this.state.animatedHeight }]}>
{this.props.options.map((option, index) => (
<MessageMenuItem
height={OPTION_HEIGHT}
icon={option.icon}
key={index}
onPress={async () => {
await this.onClose();
InteractionManager.runAfterInteractions(() => {
if (option.onPress) {
option.onPress();
}
});
}}
style={option.style}
title={option.title}
withIcon={this.props.withIcon}
/>
))}
</Animated.View>
</View>
);
}
}
DraftMenuContainer.propTypes = {
destroyMenuComponent: PropTypes.func.isRequired,
withIcon: PropTypes.bool,
options: PropTypes.arrayOf(PropTypes.shape({
icon: PropTypes.string.isRequired,
onPress: PropTypes.func,
title: PropTypes.string.isRequired,
})),
};

fetching multiple API requests in componentDidMount() function react native

I want to fetch multiple API requests in componentDidMount() function. I have a picker which take items from API call. I have another API which returns a picker value. Now i want to set selected the value received in latter API in the picker. I am trying to fetch both API in componentDidMount function
Here is my code. Please suggest where i am missing.
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, View, Platform, Picker, ActivityIndicator, Button, Alert} from 'react-native';
export default class FirstProject extends Component {
constructor(props)
{
super(props);
this.state = {
isLoading: true,
PickerValueHolder : ''
}
}
componentDidMount() {
const base64 = require('base-64');
// my API which fetches picker items
return fetch('https://reactnativecode.000webhostapp.com/FruitsList.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
// another api which fetches a particular fruit_id from database which i ave to set selected in picker.
fetch('http://my_api_url', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"fruit_id":"123"
})
}).then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
PickerValueHolder: responseJson,
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
}
GetPickerSelectedItemValue=()=>{
Alert.alert(this.state.PickerValueHolder);
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.MainContainer}>
<Picker
selectedValue={this.state.PickerValueHolder}
onValueChange={(itemValue, itemIndex) => this.setState({PickerValueHolder: itemValue})} >
{ this.state.dataSource.map((item, key)=>(
<Picker.Item label={item.fruit_name} value={item.fruit_name} key={key} />)
)}
</Picker>
<Button title="Click Here To Get Picker Selected Item Value" onPress={ this.GetPickerSelectedItemValue } />
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
justifyContent: 'center',
flex:1,
margin: 10
}
});
As you said in comment, your second API response:
[{"fruit_id": "123","fruit_name":"Strwaberry"}]
So it might work with minor changes:
.then((responseJson) => {
this.setState({
isLoading: false,
PickerValueHolder: responseJson[0].fruit_name,
}, function() {
// In this block you can do something with new state.
});
})

React Native Networking Unexpected Token in Function

I am currently trying to learn React Native, but I already struggle in the Networking Part of the Tutorial.
This is the code:
import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View } from 'react-native';
class App extends Component {
function getMoviesFromApiAsync() {
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
return responseJson.movies;
})
.catch((error) => {
console.error(error);
});
}
render() {
getMoviesFromApiAsync();
};
}
AppRegistry.registerComponent('testproject', () => App);
And I get the following error:
In my case Line 5, Char 10 would be: function so it expects something else after funct.
Here is an example of using that function:
import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View } from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = { movies: [] }
}
componentDidMount() {
this.getMoviesFromApiAsync();
}
getMoviesFromApiAsync() {
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({ movies: responseJson.movies });
})
.catch((error) => {
console.error(error);
});
}
renderMovies = () =>
this.state.movies.map((movie, i) => <Text key={i}>{movie.title}</Text>)
render() {
return (
<View style={{flex: 1}}>
{this.renderMovies()}
</View>
)
};
}
AppRegistry.registerComponent('testproject', () => App);
import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View } from 'react-native';
class App extends Component {
state = {
movies: null
}
componentDidMount() {
const movies = this.getMoviesFromApiAsync();
this.setState({movies: movies});
}
getMoviesFromApiAsync() {
return fetch('https://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
return responseJson.movies;
})
.catch((error) => {
console.error(error);
});
}
render() {
const { movies } = this.state;
if (!movies) return null;
return (
<View>
{
movies.map((movie, index) => {
console.log("movie:", movie);
return(
<View key={index}>
<Text>{movie.name}</Text>
</View>
)
})
}
</View>
)
};
}
AppRegistry.registerComponent('testproject', () => App);
1 - ) So first set variable in state movies null cause u dont have any movies data
2 - ) Read React Component Lifecycle ComponentDidMount run after render and call getMovies func for fetch data and write in the state with this.setState
3 - ) Check u have movies with if(!movies) return null; or return ActivityIndicator for loading but if u dont get movies activity indicator run forever.
4 - ) this.setState render your component again with new state

Categories

Resources