javascript – React Native Firebase Authentication On Cold Start


I am running a react native app v0.72 and am using an emulator for ios.

I am having some issues from cold start of my app gathering currentUser. Any reading online is indicating this is not available on cold start. I simply tried cheating this by implementing a 15 second delay on my app start potentially to listen for auth state changes, presuming it would only take a few seconds to generate a new token. I am unable to get the user object to have a .currentUser value after x period of time. Not really sure what the issue is here. I see auths hitting my firebase console so I know i’m signed in. I have a concern maybe the persistence isn’t’ working properly for firebase specifically. I actually store things like an email after sign in in async storage.

Here is my code that i’m using and the flow its following: My user launches the app, they navigate to my “SplashScreen.js” if they don’t have an ‘auth’ session already after x amount of time. I am signing in via apple, i get my apple oauth token back, and a firebase token as well indicating i’m logged in, it navigates me to my “HomeScreen.js”. I force close the apple app and relaunch. Then I wait again 15 seconds and i’m seeing NULL in the currentUser and i’m back at the “SplashScreen.js” because i did not authenticate in time.

Here all the major files involved (altered some files that may have sensitive like information in it so its not my true sensitive data in those spots):

App.tsx (see this referred to as app.js as well or index.js, also wondering if its worth updating this name of this file because this is what react native provides my default on init)???

import { auth } from './configs/Firestore';
import 'react-native-gesture-handler';
import React, { useEffect, useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { View, ActivityIndicator } from 'react-native';

// Import auth object from Firestore configuration
import { getAuth, onAuthStateChanged, onIdTokenChanged } from 'firebase/auth';


// Screens
import SplashScreen from './screens/SplashScreen';
import SignupScreen from './screens/CreateAccountScreen';
import LoginScreen from './screens/LoginScreen';
import InitialAccountConfigurationScreen from './screens/InitialAccountConfigurationScreen';
import ForgotPasswordScreen from './screens/ForgotPasswordScreen';
import TermsAndConditionsScreen from './screens/TermsAndConditionsScreen';
import PrivacyScreen from './screens/PrivacyPolicyScreen';
import HomeScreen from './screens/HomeScreen';
import ExerciseHomeScreen from './screens/ExerciseHomeScreen';
import CreateCustomWorkoutScreen from './screens/CreateCustomWorkoutScreen';
import FinalizeCreateCustomWorkoutScreen from './screens/FinalizeCreateCustomWorkoutScreen';
import EditCustomWorkoutNamesScreen from './screens/EditCustomWorkoutNamesScreen';
import EditCustomWorkoutDetailsScreen from './screens/EditCustomWorkoutDetailsScreen';
import AddOneExerciseScreen from './screens/AddOneExerciseScreen';
import StartExerciseNamesScreen from './screens/StartExerciseNamesScreen';
import StartExerciseWorkoutScreen from './screens/StartExerciseWorkoutScreen';
import PreviousCompletedWorkoutNamesScreen from './screens/PreviousCompletedWorkoutNamesScreen';
import PreviousCompletedWorkoutDetailsScreen from './screens/PreviousCompletedWorkoutDetailsScreen';
import HelpHomeScreen from './screens/HelpHomeScreen';
import SendFeedbackScreen from './screens/SendFeedbackScreen';
import SettingsScreen from './screens/SettingsScreen';
import EditProfileScreen from './screens/EditProfileScreen';
import ChangePasswordScreen from './screens/ChangePasswordScreen';
import DeleteAccountScreen from './screens/DeleteAccountScreen';

const Stack = createStackNavigator();

const App = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [initialLoadingComplete, setInitialLoadingComplete] = useState(false);

  // Function to create a delay
  const delay = ms => new Promise(res => setTimeout(res, ms));

  
  useEffect(() => {
    console.log("App started, setting up auth listener");
  
    const unsubscribe = onIdTokenChanged(auth, async (user) => {
      console.log("Auth State Changed");
      console.log("User from Listener:", user);
      console.log("CurrentUser from Auth:", auth.currentUser);
  
      if (user) {
        setIsAuthenticated(true);
        const idToken = await user.getIdToken();
        console.log("Firebase ID Token:", idToken);
      } else {
        setIsAuthenticated(false);
        console.log("No user found, retrying in 5 seconds");
        setTimeout(() => {
          console.log("Retry CurrentUser from Auth:", auth.currentUser);
        }, 5000);
      }
      setIsLoading(false);
    });
  
    // Start the forced 15 seconds delay
    delay(10000).then(() => setInitialLoadingComplete(true));
  
    // Cleanup function
    return () => unsubscribe();
  }, []);
  
  

  if (isLoading || !initialLoadingComplete) {
    return (
      <View style={{ flex: 1, backgroundColor: '#123456', justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" color="#FFFFFF" />
      </View>
    );
  }

  return (
    <View style={{ flex: 1, backgroundColor: '#323232' }}>
      <NavigationContainer>
        <Stack.Navigator
          initialRouteName={isAuthenticated ? 'Home' : 'Splash'}
          screenOptions={{ cardStyle: { backgroundColor: '#323232' } }}
        >
          <Stack.Screen name="Splash" component={SplashScreen} />
          <Stack.Screen name="Signup" component={SignupScreen} />
          <Stack.Screen name="Login" component={LoginScreen} />
          <Stack.Screen name="InitialAccountConfiguration" component={InitialAccountConfigurationScreen} />
          <Stack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
          <Stack.Screen name="TermsAndConditions" component={TermsAndConditionsScreen} />
          <Stack.Screen name="Privacy" component={PrivacyScreen} />
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="ExerciseHome" component={ExerciseHomeScreen} />
          <Stack.Screen name="CreateCustomWorkout" component={CreateCustomWorkoutScreen} />
          <Stack.Screen name="FinalizeCreateCustomWorkout" component={FinalizeCreateCustomWorkoutScreen} />
          <Stack.Screen name="EditCustomWorkoutNames" component={EditCustomWorkoutNamesScreen} />
          <Stack.Screen name="EditCustomWorkoutDetails" component={EditCustomWorkoutDetailsScreen} />
          <Stack.Screen name="AddOneExercise" component={AddOneExerciseScreen} />
          <Stack.Screen name="StartExerciseNames" component={StartExerciseNamesScreen} />
          <Stack.Screen name="StartExerciseWorkout" component={StartExerciseWorkoutScreen} />
          <Stack.Screen name="PreviousCompletedWorkouts" component={PreviousCompletedWorkoutNamesScreen} />
          <Stack.Screen name="PreviousCompletedWorkoutDetails" component={PreviousCompletedWorkoutDetailsScreen} />
          <Stack.Screen name="HelpHome" component={HelpHomeScreen} />
          <Stack.Screen name="SendFeedback" component={SendFeedbackScreen} />
          <Stack.Screen name="Settings" component={SettingsScreen} />
          <Stack.Screen name="EditProfile" component={EditProfileScreen} />
          <Stack.Screen name="ChangePassword" component={ChangePasswordScreen} />
          <Stack.Screen name="DeleteAccount" component={DeleteAccountScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    </View>
  );
};

export default App;

Firestore.js

import { initializeApp } from 'firebase/app';
import { getApp } from 'firebase/app';
import { getAnalytics } from "firebase/analytics";
import { getFirestore } from 'firebase/firestore';
import { getAuth, setPersistence, browserSessionPersistence } from 'firebase/auth';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getReactNativePersistence } from 'firebase/auth/react-native';
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';

const storage = getReactNativePersistence(ReactNativeAsyncStorage)
//const app = getApp();

const firebaseConfig = {
    apiKey: "AIzaSyCu2EX4S7_123_sOo8",
    authDomain: "xaza-health.firebaseapp.com",
    projectId: "xaza-health",
    storageBucket: "xaza-health.appspot.com",
    messagingSenderId: "116553309985",
    appId: "1:116553308885:web:cce248380cc9e89a3e05c",
    measurementId: "G-HV50L9NTPE",
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

console.log(auth);

// Set persistence
setPersistence(auth, getReactNativePersistence(AsyncStorage))
  .then(() => {
    console.log('Persistence set to React Native AsyncStorage');
  })
  .catch((error) => {
    console.error('Error setting persistence:', error);
  });

export { auth };

SplashScreen.js



import React, { useState, useRef, useEffect } from 'react';
import { View, Text, Platform, Animated, SafeAreaView, Image, TouchableOpacity, StyleSheet, Button } from 'react-native';
import { Path, Svg } from 'react-native-svg';
import DeviceInfo from 'react-native-device-info';
import AsyncStorage from '@react-native-async-storage/async-storage';
import EncryptedStorage from 'react-native-encrypted-storage';


// Security
import CheckVersion from '../security/CheckVersion';
import CheckSessionLoggedInAlready from '../security/CheckSessionLoggedInAlready';
import CheckSessionNotLoggedIn from '../security/CheckSessionNotLoggedIn';  // Adjust the path as necessary

// Custom Headers
import BlankHeader from '../headers/HeaderBlank';
// Custom Styles
import stylesSplashScreen from '../styles/StylesSplashScreen';
// Custom Buttons
import CustomButtonEnabled from '../buttons/CustomButtonEnabled';
import AppleSignInButton from '../buttons/AppleSignIn'; 
import GoogleSignInButton from '../buttons/GoogleSignIn'; 
// Custom Modals
import GeneralMessageModal from '../modals/GeneralMessageModal';
import GeneralMessageNoButtonsModal from '../modals/GeneralMessageNoButtonsModal';
// Firebase / OAuth
import { auth } from '../configs/Firestore'; 
import { getAuth, GoogleAuthProvider, signInWithCredential, OAuthProvider } from 'firebase/auth';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { appleAuth } from '@invertase/react-native-apple-authentication';


const AnimatedPath = Animated.createAnimatedComponent(Path);

const SplashScreen = ({ navigation }) => {
  const { shouldUpdateApp, errorMessage, setErrorMessage } = CheckVersion();
  //const { sessionUserProvider, sessionUserEmail, sessionUserUsername, sessionUserFirstName, sessionUserFirebaseUID, sessionDataIsLoaded } = CheckSessionLoggedInAlready(navigation);


  



  const [generalMessageModalVisible, setGeneralMessageModalVisible] = useState(false);
  const [generalMessageModalData, setGeneralMessageModalData] = useState({ title: '', message: '' });

  const [loggedIn, setloggedIn] = useState(false);
  const [userInfo, setuserInfo] = useState([]);

  const [isSigninInProgress, setIsSigninInProgress] = useState(false);

  const handleAppleSignIn = async () => {
    console.log("Signing in with Apple");
    try {
      // Start Apple Sign-In Request
      const appleAuthResponse = await appleAuth.performRequest({
        requestedOperation: appleAuth.Operation.LOGIN,
        requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
      });
  
      // Check credential state
      const credentialState = await appleAuth.getCredentialStateForUser(appleAuthResponse.user);
  
      if (credentialState === appleAuth.State.AUTHORIZED && appleAuthResponse.identityToken) {
        // User is authenticated and token received
        console.log('Apple SignIn Success:', appleAuthResponse);
  
        // Create Firebase credential
        const provider = new OAuthProvider('apple.com');
        const credential = provider.credential({
          idToken: appleAuthResponse.identityToken,
          rawNonce: appleAuthResponse.nonce
        });
  
        // Sign in to Firebase with the Apple credential
        const firebaseUserCredential = await signInWithCredential(auth, credential);
  
        if (firebaseUserCredential) {
          console.log('Firebase user', firebaseUserCredential.user);
          console.log("Firebase User after Sign-in:", auth.currentUser);
          const firebaseUser = firebaseUserCredential.user;
          // Log the providerData array to inspect if the email is there
          console.log('Provider Data:', firebaseUser.providerData);

          // Extract the provider ID
          const providerId = firebaseUser.providerData.find((data) => data.providerId === 'apple.com')?.providerId || 'apple.com';

          // If firebaseUser.email is undefined, try to get the email from providerData
          const emailFromProviderData = firebaseUser.providerData
            .filter((pd) => pd.providerId === 'apple.com' && pd.email)
            .map((pd) => pd.email)[0];

          // Use email from providerData if available
          const emailToUse = firebaseUser.email || emailFromProviderData;

          // Call postSignIn with the defined email, uid and providerId.
          await postSignIn({
            email: emailToUse, // Use the potentially fallback email
            firebaseuid: firebaseUser.uid,
            provider: providerId,
          });
        }
      } else {
        console.log("User not authorized or Identity token missing");
      }
    } catch (error) {
      console.log('Error with Apple Sign-In or Firebase:', error);
    }
  };


  useEffect(() => {
    // Configure Google Sign-In
    GoogleSignin.configure({
      scopes: ['https://www.googleapis.com/auth/userinfo.email'],
      iosClientId: '1:116553308885:ios:3c93a4a78b49b868a3e05c',
      webClientId: '116553308885-mvqbb5ku69pgggjl69ul2i75uhffq4b8.apps.googleusercontent.com',
    });
  }, []);
  
  const handleGoogleSignIn = async () => {
    try {
      await GoogleSignin.signOut(); // Add this line to sign the user out from Google first
      await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
      const { idToken } = await GoogleSignin.signIn();

  
      console.log('Google ID Token obtained:', idToken);
  
      const firebaseAuth = getAuth();
      const googleCredential = GoogleAuthProvider.credential(idToken);
  
      const firebaseUserCredential = await signInWithCredential(firebaseAuth, googleCredential);
      const firebaseUser = firebaseUserCredential.user;
  
      console.log('Firebase user signed in with Google:', firebaseUser);
  
      // Log the providerData array to inspect if the email is there
      console.log('Provider Data:', firebaseUser.providerData);
  
      // Extract the provider ID
      const providerId = firebaseUser.providerData.find((data) => data.providerId === 'google.com')?.providerId || 'google.com';
  
      // If firebaseUser.email is undefined, try to get the email from providerData
      const emailFromProviderData = firebaseUser.providerData
        .filter((pd) => pd.providerId === 'google.com' && pd.email)
        .map((pd) => pd.email)[0];
  
      // Use email from providerData if available
      const emailToUse = firebaseUser.email || emailFromProviderData;
  
      // Call postSignIn with the defined email, uid and providerId.
      await postSignIn({
        email: emailToUse, // Use the potentially fallback email
        firebaseuid: firebaseUser.uid,
        provider: providerId,
      });
    } catch (error) {
      console.error('Google Sign-In error:', error);
      return null;
    }
  };

  async function postSignIn({ email, firebaseuid, provider }) {
    // Your logic to handle the sign in after firebase authentication
    console.log('Post sign-in data:', { email, firebaseuid, provider });

    const payload = {
      email: email,
      firebaseuid: firebaseuid,
      provider: provider,
    };

    console.log(payload);
  
    try {
      // Start the fetch call
      const response = await fetch('https://api.xazahealth.com/loginAlt', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
      });
  
      // Parse the JSON response
      const data = await response.json();
      console.log(data);
  
      if (data.success) {
        if (data.message === 'Login Successful! Navigating to initial account configuration.') {
          console.log("Initial account configuration needed")
          // Navigate to the InitialAccountConfigurationScreen
          navigation.navigate('InitialAccountConfiguration', {
            email: email,
            firebaseuid: firebaseuid,
            provider: provider,
          });
        } else {
          try {
            // Save data to AsyncStorage and navigate
            await AsyncStorage.setItem('sessionUserProvider', data.providerid);
            await AsyncStorage.setItem('sessionUserEmail', data.email);
            await AsyncStorage.setItem('sessionUserUsername', data.username);
            await AsyncStorage.setItem('sessionUserFirstName', data.firstname || '');  // use empty string if null
            await AsyncStorage.setItem('sessionUserLastName', data.lastname || '');  // use empty string if null
            await EncryptedStorage.setItem('sessionUserFirebaseUID', data.firebaseuid);
    
            // Navigate to the Home screen
            navigation.reset({
              index: 0,
              routes: [{ name: 'Home' }],
            });
          } catch (error) {
            // Handle errors saving data
            console.log('Error saving user data:', error);
            setGeneralMessageModalData({
              success: false,
              visible: true,
              title: 'Error',
              message: data.message || 'Login failed.'
            });
          }
        }
      } else {
        // Handle login failed
        setGeneralMessageModalData({
          success: false,
          visible: true,
          title: 'Error',
          message: data.message || 'Login failed.'
        });
        setGeneralMessageModalVisible(true);
      }
    } catch (error) {
      // Handle API call error
      console.log('API call error:', error);
      setGeneralMessageModalData({
        success: false,
        visible: true,
        title: 'Service Unavailable',
        message: 'We apologize for the inconvenience! XAZA Health is offline at this time. Please try again later.'
      });
      setGeneralMessageModalVisible(true);
    }
  };


  const handleLogin = () => {
    navigation.navigate('Login');
  };

  const handleSignup = () => {
    navigation.navigate('Signup');
  };

  const animation = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.loop(
        Animated.sequence([
            Animated.timing(animation, {
                toValue: 1,
                duration: 5000,  // Increasing the duration for a smoother animation
                useNativeDriver: true,
            }),
            Animated.delay(300)  
        ])
    ).start();
  }, []);

 
  const d = animation.interpolate({
    inputRange: [
        0, 0.0625, 0.125, 0.1875, 0.25, 0.3125, 0.375, 0.4375,
        0.5, 0.5625, 0.625, 0.6875, 0.75, 0.8125, 1
    ],
    outputRange: [
        'M0,50 L40,50 L60,50 L80,50 L100,50 L120,50 L140,50 L160,50 L200,50',    // flat line
        'M0,50 L40,50 L60,45 L80,50 L100,50 L120,50 L140,50 L160,50 L200,50',    // slight rise
        'M0,50 L40,50 L60,40 L80,50 L100,50 L120,50 L140,50 L160,50 L200,50',    // P wave
        'M0,50 L40,50 L60,50 L80,53 L100,35 L120,55 L140,50 L160,50 L200,50',    // Q wave
        'M0,50 L40,50 L60,50 L80,60 L100,25 L120,60 L140,50 L160,50 L200,50',    // R wave
        'M0,50 L40,50 L60,50 L80,53 L100,45 L120,53 L140,50 L160,50 L200,50',    // S wave
        'M0,50 L40,50 L60,50 L80,55 L100,50 L120,55 L140,55 L160,50 L200,50',    // T wave
        'M0,50 L40,50 L60,50 L80,50 L100,50 L120,50 L140,50 L160,52 L200,50',    // flat line
        'M0,50 L40,50 L60,55 L80,50 L100,50 L120,50 L140,50 L160,50 L200,50',    // slight dip
        'M0,50 L40,50 L60,60 L80,50 L100,50 L120,50 L140,50 L160,50 L200,50',    // inverse P wave
        'M0,50 L40,50 L60,50 L80,47 L100,65 L120,45 L140,50 L160,50 L200,50',    // inverse Q wave
        'M0,50 L40,50 L60,50 L80,40 L100,75 L120,40 L140,50 L160,50 L200,50',    // inverse R wave
        'M0,50 L40,50 L60,50 L80,47 L100,55 L120,47 L140,50 L160,50 L200,50',    // inverse S wave
        'M0,50 L40,50 L60,50 L80,45 L100,50 L120,45 L140,45 L160,50 L200,50',    // inverse T wave
        'M0,50 L40,50 L60,50 L80,50 L100,50 L120,50 L140,50 L160,50 L200,50',     // back to flat line
    ],
  });

  /*

  useEffect(() => {
    const unsubscribe = auth().onAuthStateChanged((user) => {
      if (user) {
        console.log('User is signed in:', user);
      } else {
        console.log('User is signed out');
      }
    });
  
    return () => unsubscribe();  // Unsubscribe on component unmount
  }, []);


  */

  BlankHeader(navigation);

  return (
    <View style={stylesSplashScreen.container}>
      <View style={stylesSplashScreen.centeredContent}>
        <Text style={stylesSplashScreen.text}>
          XAZA Health
        </Text>
      </View>

      <Svg style={{ marginTop: -70 }} width="100%" height="100" viewBox="0 0 200 100">
        <AnimatedPath
          d={d}
          fill="none"
          stroke="#F9C400"
          strokeWidth="2"
        />
      </Svg>



      <View style={{ flex: 2, justifyContent: 'center' }}>
        {Platform.OS === 'android' && (
          <>
            <GoogleSignInButton onGoogleButtonPress={handleGoogleSignIn} />
            <CustomButtonEnabled 
              title="Create Account" 
              onPress={handleSignup}
            />
            <CustomButtonEnabled 
              title="Login" 
              onPress={handleLogin}
            />
          </>
        )}

        {Platform.OS === 'ios' && (
          <>
            <AppleSignInButton onAppleButtonPress={handleAppleSignIn} />
            <CustomButtonEnabled 
              title="Create Account" 
              onPress={handleSignup}
            />
            <CustomButtonEnabled 
              title="Login" 
              onPress={handleLogin}
            />
          </>
        )}
      </View>

      <View style={styles.footer}>
        <Text style={styles.footerText}>
          Signing in to this app implies continued acceptance of our{" "}
          <Text 
            style={styles.hyperlink} 
            onPress={() => navigation.navigate('TermsAndConditions')}
          >
            Terms and Conditions
          </Text>
          {" "}and{" "}
          <Text 
            style={styles.hyperlink} 
            onPress={() => navigation.navigate('PrivacyPolicy')}
          >
            Privacy Policy
          </Text>.
        </Text>
      </View>


      <GeneralMessageModal 
        generalMessageModalVisible={generalMessageModalVisible}
        generalMessageModalData={generalMessageModalData}
        handlePressingOK={() => setGeneralMessageModalVisible(false)}
      />

      <GeneralMessageNoButtonsModal 
        modalData={{
          visible: shouldUpdateApp,
          title: 'Update Required',
          message: 'You need to update the app to the latest version to continue.',
        }}
      />
      <GeneralMessageNoButtonsModal 
        modalData={{
          visible: !!errorMessage,
          title: 'Error',
          message: errorMessage,
        }}
      />
    </View>
  );
};


const styles = StyleSheet.create({
  // ...other styles,
  signInImage: {
    width: 200,
    height: 200,
    resizeMode: 'contain', // This ensures the aspect ratio is maintained
  },
  
  footer: {
    padding: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  footerText: {
    fontSize: 10,
    color: '#F5F5F5', // Adjust the color as needed
    textAlign: 'center',
  },
  hyperlink: {
    color: '#0CCAFF', // Hyperlink color
    textDecorationLine: 'underline',
  },
  
});

export default SplashScreen;

I have tried quite a bit of things (different devices, file modifcations, etc) Been working and sending this into GPT4 full files over the last few days and this thing is pretty much telling me to just ask the community, it is ‘puzzled’ as am I.

EDIT 1: Can’t include HomeScreen.js i’ve exceeded stacks limits. Also can’t include logs i’m seeing :/

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img