Home > Blockchain >  Firebase authstate takes long to authenticate user on slow connection - React Native
Firebase authstate takes long to authenticate user on slow connection - React Native

Time:04-13

I'm creating a react native app that connects to firebase. However, when I started my app in slow internet speeds, it took a while to load, and the code showed me that this was due to the wait for the authstate listener being slow on slow connections. On airplane mode, it works fine.

How can I make the app load faster, even with slow internet speeds?

import { getApps, initializeApp } from 'firebase/app';

// import 'firebase/compat/auth'
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged, signOut } from "firebase/auth";

// import 'firebase//firestore'
import { getFirestore, collection, addDoc, query, where, getDocs, deleteDoc, doc, setDoc, serverTimestamp } from "firebase/firestore"

import AsyncStorage from '@react-native-async-storage/async-storage';




// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {*****
};



// Initialize Firebase

if (!getApps().length) {
    console.log('initializing firebase');
    const app = initializeApp(firebaseConfig);
}

Packages:

{
  "name": "tut3",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "eject": "expo eject"
  },
  "dependencies": {
    "@gorhom/bottom-sheet": "^4",
    "@react-native-async-storage/async-storage": "^1.17.2",
    "@react-native-community/datetimepicker": "4.0.0",
    "@react-native-community/google-signin": "^5.0.0",
    "@react-native-firebase/app": "^14.7.0",
    "@react-native-firebase/auth": "^14.7.0",
    "@react-native-google-signin/google-signin": "^7.2.2",
    "@react-navigation/bottom-tabs": "^6.2.0",
    "@react-navigation/material-bottom-tabs": "^6.1.1",
    "@react-navigation/native": "^6.0.8",
    "@react-navigation/native-stack": "^6.5.2",
    "@react-navigation/stack": "^6.1.1",
    "expo": "~44.0.0",
    "expo-camera": "^12.1.2",
    "expo-fast-image": "^1.1.3",
    "expo-file-system": "~13.1.4",
    "expo-google-app-auth": "~8.3.0",
    "expo-image-picker": "^12.0.2",
    "expo-location": "~14.0.1",
    "expo-status-bar": "^1.2.0",
    "expo-updates": "~0.11.6",
    "firebase": "^9.6.10",
    "moment": "^2.29.1",
    "react": "17.0.1",
    "react-dom": "17.0.1",
    "react-native": "0.64.3",
    "react-native-action-button": "^2.8.5",
    "react-native-dropdown-picker": "^5.3.0",
    "react-native-gesture-handler": "~2.1.0",
    "react-native-gifted-chat": "^0.16.3",
    "react-native-image-crop-picker": "^0.37.3",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-maps": "0.29.4",
    "react-native-onboarding-swiper": "^1.2.0",
    "react-native-paper": "^4.12.0",
    "react-native-reanimated": "~2.3.1",
    "react-native-safe-area-context": "^4.2.4",
    "react-native-screens": "^3.13.1",
    "react-native-skeleton-placeholder": "^5.0.0",
    "react-native-stars": "^1.2.2",
    "react-native-vector-icons": "^9.1.0",
    "react-native-web": "0.17.1",
    "reanimated-bottom-sheet": "^1.0.0-alpha.22",
    "shorthash": "^0.0.2",
    "styled-components": "^5.3.5"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9"
  },
  "private": true
}

Here is my routes folder that determines what to show on the app until the app initialization is done:

import React, { useContext, useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';

import { auth } from './AuthProvider';
// import 'firebase/compat/auth'
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged, signOut } from "firebase/auth";

// import 'firebase//firestore'
import { getFirestore, collection, addDoc, query, where, getDocs, deleteDoc, doc, setDoc } from "firebase/firestore"


import { AuthContext } from './AuthProvider';

import AuthStack from './AuthStack';
import AppStack from './AppStack';
import AsyncStorage from '@react-native-async-storage/async-storage';



const Routes = () => {
  const { user, setUser } = useContext(AuthContext);
  const [initializing, setInitializing] = useState(true);

  const onAuthStateChanges = (user) => {
    setUser(user);
    if (initializing) setInitializing(false);
    console.log(user)
  };
  
  useEffect(() => {
    
    const subscriber = onAuthStateChanged(auth, onAuthStateChanges);
    return subscriber; // unsubscribe on unmount
    
  }, []);

  if (initializing) return null;


  return (
    <NavigationContainer>
      {user ? <AppStack /> : <AuthStack />}
    </NavigationContainer>
  )
  
};

export default Routes;

Thanks!

EDIT: After the suggested answer, this is what I've implemented that seems to be working for me. Idea is if the user was previously logged in and exited the app without logging out, then load the appstack.

const Routes = () => {
  const { user, setUser } = useContext(AuthContext);
  const [initializing, setInitializing] = useState(true);
  const [previousUser, setPreviousUser] = useState(null);

  const onAuthStateChanges = (user) => {
    setUser(user);
    AsyncStorage.setItem('user', JSON.stringify(user));
    if (initializing) setInitializing(false);
    console.log(user)
  };

  useEffect(() => {
    AsyncStorage.getItem('user').then((value) => {
      if (value) {
        setPreviousUser(true)
        setUser(JSON.parse(value))
      }
      else {
        setPreviousUser(false)
      }

    })


    const subscriber = onAuthStateChanged(auth, onAuthStateChanges);
    return subscriber; // unsubscribe on unmount

  }, []);

  if (initializing && user) return (
    <NavigationContainer>
      <AppStack />
    </NavigationContainer>
  ) 

  if (initializing && !user) return (
    <NavigationContainer>
      <AuthStack />
    </NavigationContainer>
  ) 

  return (


    <NavigationContainer>
      {!initializing && user ? <AppStack /> : <AuthStack />}
    </NavigationContainer>

  )



};

CodePudding user response:

The call to initializeApp is synchronous and doesn't need to call the server, so should run pretty quickly and at a speed unrelated to your internet connection.

But onAuthStateChanged waits for the auth state to be restored, which does require a call to the servers to check whether the user's credentials are still valid (for example, that their account wasn't disabled). On a slow internet connection this may take some time.

Since most of such calls will be successful, a common trick is to assume they will succeed and then course correct if it turns out that assumption was wrong. To implement this, you'll need to store a token value in local storage when the user has successfully signed in, and then use that value to determine your initial navigation instead of waiting for onAuthStateChanged.

Firebaser Michael Bleigh talked about this at Google I/O in this video on Architecting Mobile Web Apps.

  • Related