How to implement multiple auth providers with react native expo-auth-session and firebase? - javascript

Hello and thanks in advance for your help.
I am struggling with implementing various auth providers with Firebase & expo-auth-session.
basically, the below code works well for Google login but I'd like to put all the "Google" logic in a separate file and add other files for additional providers. So that I can call the relevant function from the LoginScreen.js. Below the working code, I also pasted what I've tried but it resulted in having a hook error (Invalid hook call. Hooks can only be called inside of the body of a function component).
Has anyone an idea on how to manage this promptAsync() with multiple providers and separated files ?
import React, {useState, useEffect} from "react";
import { View, StyleSheet } from "react-native";
import { AppTitle, Screen, AppLink, SpecialText, AppButtonPrimary, AppText, ActivityIndicator } from "../../components";
import routes from "../../navigation/routes";
import colors from "../../config/colors";
import * as WebBrowser from 'expo-web-browser';
import { signInWithCredential, GoogleAuthProvider } from "firebase/auth";
import { auth } from '../../config/firebase';
import * as Google from 'expo-auth-session/providers/google';
import logger from '../../utility/logger'
WebBrowser.maybeCompleteAuthSession();
function LoginScreen({ navigation }) {
const [loadingPage, setLoadingPage] = useState(false)
const [request, response, promptAsync] = Google.useIdTokenAuthRequest(
{
clientId: 'xxxxxxxxxx.apps.googleusercontent.com',
},
);
useEffect(() => {
if (response?.type === 'success') {
setLoadingPage(true)
const { id_token } = response.params;
const credential = GoogleAuthProvider.credential(id_token);
logUser(auth, credential);
}
}, [response]);
async function logUser(auth, credential) {
try {
const result = await signInWithCredential(auth, credential)
const user = result.user // User is signed in
// Some code
} catch(err) {
logger.log(err)
// Some code
}
}
return (
<Screen style={styles.container}>
<ActivityIndicator visible={loadingPage}/>
<AppTitle style={styles.title} size={30}>Connectez-vous à votre compte</AppTitle>
<View style={styles.buttons}>
<AppButtonPrimary
text="Email"
icon="envelope-o"
onPress={() => navigation.navigate(routes.LOGIN_EMAIL_SCREEN)}
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
<AppButtonPrimary
text="Google"
icon="google"
onPress={() => {promptAsync()}} // <=== how to pass a function here instead of this promptAsync ?!
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
},
title: {
marginTop: 30,
marginHorizontal:10,
textAlign: 'center'
},
buttons: {
width: "100%",
alignItems: "center",
marginTop: 20,
}
});
export default LoginScreen;
// HERE IS WHAT I'VE TRIED
// LoginScreen.js
import React, {useState} from "react";
import { View, StyleSheet } from "react-native";
import { AppTitle, Screen, AppLink, SpecialText, AppButtonPrimary, AppText, ActivityIndicator } from "../../components";
import routes from "../../navigation/routes";
import colors from "../../config/colors";
import googleAuth from "../../auth/googleAuth";
import facebookAuth from "../../auth/facebookAuth";
function LoginScreen({ navigation }) {
const [loadingPage, setLoadingPage] = useState(false)
function googleSignIn() {
setLoadingPage(true)
googleAuth()
}
return (
<Screen style={styles.container}>
<ActivityIndicator visible={loadingPage}/>
<AppTitle style={styles.title} size={30}>Connectez-vous à votre compte</AppTitle>
<View style={styles.buttons}>
<AppButtonPrimary
text="Email"
icon="envelope-o"
onPress={() => navigation.navigate(routes.LOGIN_EMAIL_SCREEN)}
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
<AppButtonPrimary
text="Google"
icon="google"
onPress={googleSignIn} // <=== here
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
<AppButtonPrimary
text="Facebook"
icon="facebook"
onPress={facebookSignIn} // <=== this is the idea (having multiple providers with their own logic in a separate file)
width={"80%"}
backColor={colors.white}
textColor={colors.grayDark}
/>
</View>
</Screen>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
},
title: {
marginTop: 30,
marginHorizontal:10,
textAlign: 'center'
},
buttons: {
width: "100%",
alignItems: "center",
marginTop: 20,
}
});
export default LoginScreen;
// And the googleAuth.js
// Infrastructure
import { useEffect } from "react"
import { ResponseType } from 'expo-auth-session';
import * as Google from 'expo-auth-session/providers/google';
import * as WebBrowser from 'expo-web-browser';
// Firebase
import { signInWithCredential, GoogleAuthProvider } from "firebase/auth";
import { auth } from '../../app/config/firebase';
WebBrowser.maybeCompleteAuthSession();
function googleAuth() {
const [request, response, promptAsync] = Google.useIdTokenAuthRequest(
{
clientId: 'xxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
},
);
useEffect(() => {
if (response?.type === 'success') {
const { id_token } = response.params;
const credential = GoogleAuthProvider.credential(id_token);
logUser(auth, credential);
}
}, [response]);
async function logUser(auth, credential) {
try {
const result = await signInWithCredential(auth, credential)
const user = result.user // User is signed in
// Some Code
} catch(err) {
// Some Code
}
}
}
export default googleAuth

Related

React Native Image Component showing white blank

Hi I am new to react native and have been playing around some components on the doc.
I wrote some code for an Image component, but it shows white blank even though I can check the path to the image stored in Firestore. Uploading and storing an image is successfully done, and I can see the path in the Firebase console (also, I can see the path on the screen).
Here is the code Image component placed in.
import {
Image,
ImageBackground,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import { useAuthContext } from "../../contexts/AuthContext";
import { NotYetVerified } from "./NotYetVerified";
import { SignoutBtn } from "./SignoutBtn";
import { useFirestoreContext } from "../../contexts/FirestoreContext";
import { ImagePickerBtn } from "./ImagePickerBtn";
import { useEffect } from "react";
export const UserProfile = () => {
const { currUser } = useAuthContext();
const { getUserInfo, userInfo } = useFirestoreContext();
useEffect(() => {
getUserInfo(currUser.email);
}, []);
console.log("UserProfile.js userInfo: ", userInfo.photoUrl, currUser.photoURL);
return (
<>
{/* remove the ! mark when publishing */}
{currUser.emailVerified ? (
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<TouchableOpacity onPress={() => getUserInfo(currUser.email)}>
<Text>Get user info</Text>
</TouchableOpacity>
<Text>{userInfo?.email}</Text>
<Text>{userInfo?.photoUrl}</Text>
<ImagePickerBtn />
<Image
style={styles.photo}
source={{
uri: userInfo.photoUrl,
}}
resizeMethod="resize"
resizeMode="contain"
/>
</View>
) : (
<NotYetVerified />
)}
<SignoutBtn />
</>
);
};
const styles = StyleSheet.create({
photo: {
width: 100,
height: 100,
borderRadius: 50,
borderColor: "black",
borderWidth: 1,
},
});
I don't think it's necessary, but just in case, code below is for uploading a file.
import React, { useState, useEffect } from "react";
import { Button, Image, View, Platform } from "react-native";
import * as ImagePicker from "expo-image-picker";
import {getDownloadURL, getStorage, ref, uploadBytes} from 'firebase/storage'
import { useAuthContext } from "../../contexts/AuthContext";
import { doc, setDoc, updateDoc } from "firebase/firestore";
import { db } from "../../firebase.config";
import { updateProfile } from "firebase/auth";
export const ImagePickerBtn = () => {
const [image, setImage] = useState(null);
const {currUser} = useAuthContext()
const pickImage = async () => {
// No permissions request is necessary for launching the image library
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
console.log(result);
if (!result.canceled) {
const storage = getStorage(); // the storage itself
const fileRef = ref(storage, currUser.uid + '.png'); // how the image will be addressed inside the storage
// convert image to array of bytes.
const img = await fetch(result.uri);
const blob = await img.blob();
await uploadBytes(fileRef, result.uri); // upload the image
const url = await getDownloadURL(fileRef); // get the url of the image
console.log(url); // print the url of the image
await updateDoc(doc(db, "users", currUser.uid), {photoUrl: url}); // set the url of the image in the database
await updateProfile(currUser, {photoURL: url}); // set the url of the image in the database})
}
};
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
{image && (
<Image source={{ uri: image }} style={{ width: 200, height: 200 }} />
)}
</View>
);
}

How to display all images from firebase storage on React Native app?

I can upload pictures to FB storage. But now I'm trying to display them all on React Native app live.
For some reason, I can't make it work. There are not lots of videos on youtube or recent tutorials on how to do this. I'm trying to make it work by looking it up on Stackoverflow from people who had some similar problems, but no luck so far.
Here's my app code
import { StyleSheet, View } from 'react-native';
import Uploadscreen from './src/UploadSreen';
import ListPictures from './src/ListPictures';
export default function App() {
return (
<View style={styles.container}>
<Uploadscreen/>
<ListPictures/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
The UploadScreen component works totally fine (this is the one uploading to FB)
And here's my separate component for looping all the images in firebase storage(Which I need help with).
import { firebase } from '../config'
import { View, Image } from 'react-native';
import React, { useState } from 'react';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
const ListPictures = () => {
const [sampleImage, setSampleImage] = useState();
const getSampleImage = async () => {
const imageRefs = await firebase.storage().ref().child('Front/').listAll();
const urls = await Promise.all(imageRefs.items.map((ref) => ref.getDownloadURL()));
setSampleImage(urls);
}
{ sampleImage && getSampleImage().map(url => (
<View style={{ justifyContent: 'center' }} key={imageRef.id}>
<Image source={{ uri: url }} style={{ width: 350, height: 350 }} />
</View>
))}
}
export default ListPictures;
Any help would be much appreciated!
Try this
import { firebase } from '../config'
import { View, Image } from 'react-native';
import React, { useState, useEffect } from 'react';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
const ListPictures = () => {
const [sampleImage, setSampleImage] = useState([]);
const getSampleImage = async () => {
const imageRefs = await firebase.storage().ref().child('Front/').listAll();
const urls = await Promise.all(imageRefs.items.map((ref) => ref.getDownloadURL()));
setSampleImage(urls);
}
useEffect(()=>{
getSampleImage()
},[])
{ sampleImage.length!=0 && sampleImage.map(url => (
<View style={{ justifyContent: 'center' }} key={imageRef.id}>
<Image source={{ uri: url }} style={{ width: 350, height: 350 }} />
</View>
))}
}
export default ListPictures;
You shouldn't call asynchronous code while building the UI output as you do here:
{ sampleImage && getSampleImage().map(url => (
What you have will be called on every render, which is likely not what you want.
Instead, put such a call in a useEffect hook with an empty dependencies array:
useEffect(() => {
getSampleImage();
}, []);
This way the call to getSampleImage() runs when the component gets created, rather than on each render.

React native - create one common function that can be shared between 2 or more screens

I have 2 screens, SignUpScreen and CreateCommunityScreen, and these 2 screens have an icon which calls the same function pickImage whenever the icon is clicked. How do I create one function for both screens? Here's what I have so far, but I encountered the error "Error: You attempted to set the key _V with the value 1 on an object that is meant to be immutable and has been frozen." Thanks in advance.
pickImage function
import * as ImagePicker from "expo-image-picker";
const pickImage = async () => {
let imageURI = "";
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status === "granted") {
let selectedImage = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!selectedImage.cancelled) {
imageURI = selectedImage.uri;
}
return imageURI;
}
};
export default pickImage;
SignUpScreen
import React, { useState } from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import AppBrand from "../../component/AppBrand";
import AuthenticationForm from "../../component/AuthenticationForm";
import CustomButtonLink from "../../component/CustomButtonLink";
import DefaultProfileIcon from "../../component/DefaultProfileIcon";
import pickImage from "../../helper/pickImage";
const SignUpScreen = ({ navigation }) => {
const [image, setImage] = useState(null);
return (
<View>
<AppBrand />
<DefaultProfileIcon
onPress={() => {
setImage(pickImage);
console.log(image);
}}
/>
<AuthenticationForm
btnName={"SIGN UP"}
onNavigate={() => {
console.log("image", image);
console.log("Stays on Sign Up");
}}
/>
<CustomButtonLink
custBtnLinkName={"Cancel"}
style={styles.spacing_Cancel}
onNavigate={() => {
navigation.navigate("Sign In");
}}
/>
{image && (
<Image source={{ uri: image }} style={{ width: 200, height: 200 }} />
)}
</View>
);
};
const styles = StyleSheet.create({
spacing_Cancel: {
marginTop: 170,
alignItems: "center",
},
});
export default SignUpScreen;
Create a file where all the common functions are present (eg. Helper.js)
Add the functions in helper file and export it
export function getIcon(){
Console.log("Get icon function called")
}
Now import the file where you want to use it in different screens
eg:
import Helper from '..path of the file'
and use it like
Helper.getIcon()

React-Native: Can't Navigate to the Second Screen

sorry if my question is a bit long but this is my first one here and I'm really desperate to know what's wrong with my code. Thank you in advance
Please I need some help with my React-Native app. So in short, my app is about a login screen (username and password) with a checkbox to save login and a login button. After entering the username and password the second screen should be a simple Welcoming screen: "Welcome {userName}". If the user checks the save login box and presses login, the app will navigate to the second screen and he can't go back (no back button) and the next time the user opens the app it directly takes him/her to the second screen.
So, my problem is that I can't navigate to the second screen and I don't know how to remove the back button.
Here is my code:
App.js:
import React, { Component } from 'react';
import { SafeAreaView,Text,Button } from 'react-native';
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer } from "react-navigation";
import FirstScreen from './first';
import SecondScreen from './second';
import myDB from './myDB';
const AppNavigator = createStackNavigator({
Login : FirstScreen,
Welcome: SecondScreen
},
{
initialRouteName : "Login"
})
const AppContainer = createAppContainer(AppNavigator)
export default class App extends React.Component{
render()
{
return<AppContainer/>;
}
}
first.js:
import React, { Component } from 'react';
import {
SafeAreaView,
Text,
Button,
TextInput,
CheckBox,
StyleSheet
} from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import myDB from './myDB';
class FirstScreen extends Component {
constructor (props) {
super(props);
}
state = {
userName: '',
password: '',
chkValue: false,
};
handleUserName = (text) => {
this.setState({ userName: text });
};
handlePasword = (text1) => {
this.setState({ password: text1 });
};
openSecondScreen = () => {
myDB.getData.userName = this.state.userName;
myDB.getData.password = this.state.password;
this.props.navigation.navigate('second');
};
chkChange = (text2) => {
this.setState({ chkValue: text2 });
};
render() {
return (
<SafeAreaView style={{ alignItems: 'center' }}>
<Text>Username</Text>
<TextInput
style={styles.input}
value={this.state.userName}
placeholderTextColor="white"
onChangeText={this.handleUserName}
/>
<Text>Password</Text>
<TextInput
style={styles.input}
value={this.state.password}
placeholderTextColor="white"
onChangeText={this.handlePasword}
secureTextEntry="true"
/>
<SafeAreaView style={styles.checkboxContainer}>
<CheckBox
value={this.state.chkValue}
onValueChange={this.chkChange}
style={styles.checkbox}
/>
<Text style={styles.label}>Save Login</Text>
</SafeAreaView>
<SafeAreaView style={styles.view1}>
<Button
style={styles.btn1}
title="Login"
onPress={this.openSecondScreen}></Button>
</SafeAreaView>
</SafeAreaView>
);
}
}
export default FirstScreen;
const styles = StyleSheet.create({
input: {
height: 30,
backgroundColor: 'white',
paddingLeft: 15,
paddingRight: 15,
margin: 8,
},
view1: {
flexDirection: 'column',
},
checkboxContainer: {
flexDirection: 'row',
marginBottom: 20,
},
checkbox: {
alignSelf: 'center',
},
label: {
margin: 8,
},
});
second.js:
import React, { Component } from 'react';
import { SafeAreaView, Text, Button } from 'react-native';
import myDB from './myDB';
class SecondScreen extends Component {
state = {
userName: myDB.getData.userName,
password: myDB.getData.password
};
render() {
const { navigation } = this.props;
const userName = navigation.getParam('userName', 'No-User');
const password = navigation.getParam('password', 'No-User');
return (
<SafeAreaView style={{ alignItems: 'center' }}>
<Text>Welcome {this.state.userName}</Text>
</SafeAreaView>
);
}
}
export default SecondScreen;
myDB.js:
import React, { Component } from 'react';
class myDb extends Component {
static myVariable = {
userName :'',
password:''
};
static getData = (myVariable) => {
return myVariable;
};
}
export default myDb;
I use snack.expo.io to build these apps just in case if it helps.
Have a try with the below small change
this.props.navigation.navigate('second');
to
this.props.navigation.navigate('Welcome');
as we have to use the key we have specified in the navigator to navigate to a specific screen.

react-navigation drawer updating multiple times

I am building an application with React Native and React Navigation, I have made all the settings and it is working, however, when the drawer is fired the image is updated multiple times causing spikes and failures to trigger buttons contained in it.
e.g.:
I am using:
react: 16.8.3,
react-native: 0.59.1,
react-native-ui-kitten: ^3.1.2,
react-navigation: ^3.4.0
I was using version 3 of RN and to try to solve I went back to version 2 but without success.
I put some warnings in the method that executes the image and saw that it is called whenever there is this update.
I already changed the image in different sizes and formats but it also did not help.
I already tested on cell phones and emulators but with no success.
Drawer:
import React, { Component } from 'react';
import {
TouchableHighlight,
View,
ScrollView,
Image,
Platform,
StyleSheet,
} from 'react-native';
import {
RkStyleSheet,
RkText,
RkTheme,
} from 'react-native-ui-kitten';
import Icon from 'react-native-vector-icons/Ionicons';
import Routes from '../../config/navigation/routes';
import logo from '../../assets/smallLogo.png';
export default function SideNavigation(props) {
const onMenuItemPressed = item => {
props.navigation.navigate(item.id);
};
const renderIcon = () => (<Image style={styles.image} source={logo}/>);
const renderMenuItem = item => (
<TouchableHighlight style={styles.container} key={item.id} underlayColor={RkTheme.current.colors.button.underlay} activeOpacity={1} onPress={() => onMenuItemPressed(item)}>
<View style={styles.content}>
<View style={styles.content}>
<RkText style={styles.icon} rkType='moon primary xlarge'><Icon name={item.icon} size={25}/></RkText>
<RkText rkType='regular'>{item.title}</RkText>
</View>
{/*<RkText rkType='awesome secondaryColor small'>{FontAwesome.chevronRight}</RkText>*/}
</View>
</TouchableHighlight>
);
const renderMenu = () => Routes.map(renderMenuItem);
return (
<View style={styles.root}>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={[styles.container, styles.content]}>
{renderIcon()}
</View>
{renderMenu()}
</ScrollView>
</View>
)
}
const styles = RkStyleSheet.create(theme => ({
container: {
height: 60,
paddingHorizontal: 16,
borderTopWidth: StyleSheet.hairlineWidth,
borderColor: theme.colors.border.base,
},
root: {
paddingTop: Platform.OS === 'ios' ? 20 : 0,
backgroundColor: theme.colors.screen.base
},
content: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
},
icon: {
marginRight: 13,
},
image:{
resizeMode: 'contain',
maxWidth: 125
}
}));
Drawer setup:
import React, {Component} from 'react';
import { View, Text} from 'react-native';
import Login from './screens/login';
import PasswordRecovery from './screens/passwordRecovery';
import Home from './screens/home';
import SideNavigator from './components/sideMenu';
import { bootstrap } from './config/bootstrap';
import {
createDrawerNavigator,
createStackNavigator,
createAppContainer
} from 'react-navigation';
import { withRkTheme } from 'react-native-ui-kitten';
import NavBar from './components/navBar';
import AppRoutes from './config/navigation/routesBuilder';
import Splash from './screens/splash';
bootstrap();
const renderHeader = (navigation, props) => {
const ThemedNavigationBar = withRkTheme(NavBar);
return (
<ThemedNavigationBar navigation={navigation} headerProps={props} />
);
};
const App = createStackNavigator({
Splash: Splash,
Login: Login,
PasswordRecovery: PasswordRecovery,
Main: createDrawerNavigator({
...AppRoutes
},{
contentComponent: props => {
const SideNav = withRkTheme(SideNavigator);
return <SideNav {...props}/>
}
}),
},
{
headerMode: 'none',
})
export default createAppContainer(App);
Routes setup:
import React from 'react';
import _ from 'lodash';
import { createStackNavigator } from 'react-navigation';
import { withRkTheme } from 'react-native-ui-kitten';
import transition from './transitions';
import Routes from './routes';
import NavBar from '../../components/navBar';
const main = {};
const flatRoutes = {};
const routeMapping = (route) => ({
screen: withRkTheme(route.screen),
title: route.title,
});
(Routes).forEach(route => {
flatRoutes[route.id] = routeMapping(route);
main[route.id] = routeMapping(route);
route.children.forEach(nestedRoute => {
flatRoutes[nestedRoute.id] = routeMapping(nestedRoute);
});
});
const renderHeader = (navigation, props) => {
const ThemedNavigationBar = withRkTheme(NavBar);
return (
<ThemedNavigationBar navigation={navigation} headerProps={props} />
);
};
const DrawerRoutes = Object.keys(main).reduce((routes, name) => {
const rawRoutes = routes;
rawRoutes[name] = {
name,
screen: createStackNavigator(flatRoutes, {
initialRouteName: name,
headerMode: 'screen',
cardStyle: { backgroundColor: 'transparent' },
transitionConfig: transition,
defaultNavigationOptions: ({ navigation }) => ({
gesturesEnabled: false,
header: (props) => renderHeader(navigation, props),
}),
}),
};
return rawRoutes;
}, {});
const AppRoutes = DrawerRoutes;

Categories