在本教程中,我们将用Expo建立一个与Firebase认证模块集成的移动应用。到最后,我们将拥有一个运行中的移动应用程序,具有以下功能。
- 注册和登录屏幕(使用电子邮件和密码)
- 用户的主屏幕只限于登录的用户
- 使用React Navigation的动态屏幕路由
本教程的源代码可以在GitHub上找到。可以通过下面的链接自由跳转到你最感兴趣的部分。
用Expo创建一个React Native应用
在我们开始与Firebase集成之前,我们需要用Expo CLI来设置我们的项目。
如果你的系统中没有安装它,你将不得不运行。
npm install --global expo-cli
一旦你安装了它,你就可以用以下命令创建一个新的项目。注意,我们的项目名称是expo-with-firebase-auth-demo ,但你可以用不同的名称。
expo init expo-with-firebase-auth-demo
在项目准备就绪之前,Expo会要求你提供你想要的模板,并会提供以下选项。

就个人而言,我总是选择blank (TypeScript) 。你可以选择blank ,当然,但为什么不试试TypeScript呢?它很厉害。
在做出选择后,你将准备继续安装我们将需要的额外软件包。
对于那些额外的库,运行。
expo install @react-navigation/native @react-navigation/stack firebase dotenv react-native-elements expo-constants
注意,我们特意安装了包firebase ,也就是JavaScript SDK,而不是react-native-firebase 。这是由于该库在Expo Go应用中的限制。你可以在Expo官方文档或Firebase文档中阅读更多相关内容。
接下来,我们将在Firebase上设置云项目。
在Firebase上设置云项目
要开始的话,你需要一个Firebase项目。如果你还没有一个Firebase账户,请访问firebase.google.com/,然后创建一个。之后,登录到你的Firebase控制台,用 "创建项目 "按钮创建一个新的项目。

接下来,添加新的Firebase项目的名称。注意,它不一定要和世博项目的名称一样,然后点击继续。

在这一步,你可以决定是否要选择加入谷歌分析。尽管它为项目提供了一些有趣的功能,但我们的演示并不需要它,所以我将禁用它。
一旦完成,点击创建项目。

一旦进入你的项目界面,你需要设置一个应用程序。记住,一个Firebase项目可以承载多个应用,包括网站、unity和移动应用。
点击Web项目应用来创建一个新的Web项目。我们必须选择Web选项而不是本地选项,因为我们使用了Firebase JS SDK。

输入应用程序的详细信息,并确保Firebase托管被禁用。一旦完成,点击注册应用程序。

你会在屏幕上收到设置配置的说明,包括你的应用密钥。这些密钥不是你的私人密钥,但它们是访问后端服务所必需的,因为它们允许你将你的代码与Firebase云整合起来。
在代码中留下它们并不是一个好主意,但现在我们不会关注这个。复制给定的代码并保存。当我们设置环境变量时,我们会再来讨论这个话题。

现在应用程序已经准备好了,我们可以开始添加Firebase服务,比如说认证。

你可以从左边的Build菜单或主屏幕上的产品部分访问认证服务。在那里,点击 "开始",选择你喜欢的登录方式。
我们今天的演示将使用电子邮件/密码提供者,但可以自由地探索更多的选项。Firebase在这里提供了大量的选择。

一旦你选择了一个提供者,只需启用它,如果需要的话,按照说明进行设置并保存你的改动。

在你的Expo应用程序中配置Firebase SDK
现在是设置Firebase SDK的时候了。为此,我们将把上面提供的Firebase设置代码添加到我们的应用程序中。让我们在应用程序的源文件中创建一个名为config 的文件夹,并添加一个名为firebase.ts 的文件。
在那里,我们要粘贴配置代码。
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// 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
const firebaseConfig = {
apiKey: "AIzaSyBSKowWPBCynHTYSFBBnJUqP2pI-pr2GJI",
authDomain: "expo-with-firebase-auth-demo.firebaseapp.com",
projectId: "expo-with-firebase-auth-demo",
storageBucket: "expo-with-firebase-auth-demo.appspot.com",
messagingSenderId: "719336217671",
appId: "1:719336217671:web:efd7e14078045dc9a72562"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export default app;
然而,如果我们让它保持原样,我们的钥匙和敏感设置可能会被暴露。相反,我们将使用环境变量将这些信息从我们的提交中排除。
使用环境变量保护Firebase设置
首先,将配置移到位于项目根部的.env 文件中。
FIREBASE_API_KEY=AIzaSyBSKowWPBCynHTYSFBBnJUqP2pI-pr2GJI
FIREBASE_AUTH_DOMAIN=expo-with-firebase-auth-demo.firebaseapp.com
FIREBASE_PROJECT_ID=expo-with-firebase-auth-demo
FIREBASE_STORAGE_BUCKETt=expo-with-firebase-auth-demo.appspot.com
FIREBASE_MESSAGING_SENDER_ID=719336217671
FIREBASE_APP_ID=1:719336217671:web:efd7e14078045dc9a72562
当然,你应该从你自己的项目中提供这些值,而不是使用我的。
不要忘记把这个文件添加到你的.gitignore 。否则,它可能会被提交并意外推送。
与我们可以用CRA做的不同,在访问这些值之前,需要在Expo中进行一些额外的配置。
- 把你的应用程序设置从
app.json改为app.config.js。这样一来,我们就可以访问变量process。 - 导出文件内的JSON对象,在文件的开头添加
export default - 在文件的开头添加
import 'dotenv/config'。 - 添加一个额外的部分来映射我们的环境变量和代码中可访问的常量。新的部分如下。
"extra": {
firebaseApiKey: process.env.FIREBASE_API_KEY,
firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN,
firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
firebaseAppId: process.env.FIREBASE_APP_ID
}
最后,你的文件应该看起来像这样。
import 'dotenv/config';
export default {
"expo": {
"name": "expo-with-firebase-auth-demo",
"slug": "expo-with-firebase-auth-demo",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
},
"extra": {
firebaseApiKey: process.env.FIREBASE_API_KEY,
firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN,
firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
firebaseAppId: process.env.FIREBASE_APP_ID
}
}
}
现在,由于已经安装的软件包expo-constants ,对象的extra 部分中的所有键都可以在整个应用程序中访问。
最后一步是重新编写firebase.ts 配置文件,以使用新的常量而不是硬编码这些键。这个改变很简单,就是用你的配置文件中定义的名称来改变常量的值。
新的 firebase.ts 文件应该看起来像这样。
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import Constants from 'expo-constants';
// 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
const firebaseConfig = {
apiKey: Constants.manifest?.extra?.firebaseApiKey,
authDomain: Constants.manifest?.extra?.firebaseAuthDomain,
projectId: Constants.manifest?.extra?.firebaseProjectId,
storageBucket: Constants.manifest?.extra?.firebaseStorageBucket,
messagingSenderId: Constants.manifest?.extra?.firebaseMessagingSenderId,
appId: Constants.manifest?.extra?.firebaseAppId,
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export default app;
添加导航
现在是时候布局我们的应用导航和用户流了,考虑到应用中的两种不同状态:登录和注销的情况。
下面是我们的导航将如何工作。
- 登出的用户
- 欢迎屏幕
- 登录界面
- 注册屏幕
- 欢迎屏幕
- 已登录的用户
- 首页
让我们把重点放在帮助器和导航的编程上。我们将首先创建屏幕占位符,以使一切都在正确的位置。
让我们创建一个名为screens 的新文件夹,为我们的每个屏幕创建一个文件。现在,所有的屏幕都将有相同的设计。我们将在以后解决这个问题。
你的文件夹结构看起来像这样。

每个文件将包含以下代码。
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text>Home screen!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
只要确保适当地重命名这些组件。
创建一个认证钩子
首先,我们必须识别一个用户是否被认证,所以我们将建立一个自定义的钩子,它将执行该信息,以及用户信息,如果有一个用户登录的话。
创建一个名为utils 的文件夹,并在里面建立一个新的文件夹hooks ,在这个文件夹下我们将放置一个新的文件,useAuthentication.ts 。
该文件的代码是这样的。
import React from 'react';
import { getAuth, onAuthStateChanged, User } from 'firebase/auth';
const auth = getAuth();
export function useAuthentication() {
const [user, setUser] = React.useState<User>();
React.useEffect(() => {
const unsubscribeFromAuthStatuChanged = onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
setUser(user);
} else {
// User is signed out
setUser(undefined);
}
});
return unsubscribeFromAuthStatuChanged;
}, []);
return {
user
};
}
通过调用onAuthStateChanged ,我们订阅了一个事件,该事件在每次授权状态发生变化时都会触发,例如当用户登录或退出应用程序时。
我们使用这个事件来捕获用户信息,并将其正确地设置为钩子状态,然后将其提供给可能需要它的组件。
创建导航路由器
一旦认证工具做好了,我们就可以为认证和未认证的用户建立导航栈。
我们将把所有的导航逻辑分成三个文件。
- 一个
index,它将处理所有状态下的应用范围的导航。 - 一个
authStack,包括所有未认证用户的堆栈 - 一个用于登录用户的
userStack
让我们从authStack.tsx. 开始 在项目根目录上的navigation 文件夹下创建这个新文件,该目录也必须创建该文件夹。在那里,放置以下代码。
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import WelcomeScreen from '../screens/Welcome';
import SignInScreen from '../screens/SignInScreen';
import SignOutScreen from '../screens/SignUpScreen';
const Stack = createStackNavigator();
export default function AuthStack() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Welcome" component={WelcomeScreen} />
<Stack.Screen name="Sign In" component={SignInScreen} />
<Stack.Screen name="Sign Up" component={SignOutScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
接下来,让我们研究一下userStack.tsx 文件。
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/Home';
const Stack = createStackNavigator();
export default function UserStack() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
最后,我们用 index.tsx 文件将二者连接起来。
import React from 'react';
import { useAuthentication } from '../utils/hooks/useAuthentication';
import UserStack from './userStack';
import AuthStack from './authStack';
export default function RootNavigation() {
const { user } = useAuthentication();
return user ? <UserStack /> : <AuthStack />;
}
这个最新的组件使用useAuthentication 钩子来确定我们是否有一个登录的用户,在此基础上,它加载两个应用程序堆栈中的一个。
最后,我们需要将我们的主App.tsx 与导航连接起来,尽管我们还没有屏幕,但我们应该看到加载的是默认的Welcome 屏幕,因为我们没有登录。
import React from 'react';
import './config/firebase';
import RootNavigation from './navigation';
export default function App() {
return (
<RootNavigation />
);
}
到此为止,我们可以通过运行应用程序来确保一切工作正常。
expo start
在你的设备、模拟器或网络上运行该项目后,你应该看到像这样的东西。

这不是很令人印象深刻,但我们接下来会在屏幕上做文章。
构建屏幕
构建屏幕无疑是一个应用程序中最重要的方面之一。在本教程中,我们将构建基础部分,而不太关注整体设计。
我们需要设置我们的UI库,我们已经在教程开始时安装了这个库,但还有一些缺失的配置步骤需要说明。
设置react-native-elements
我们不会在这里创建一个自定义主题,但我们确实需要导入库的默认主题。回到App.tsx ,按照官方文档中的详细说明添加主题提供者。
现在,只需将App.tsx 中的所有组件包裹到主题提供者中,如下所示。
import React from 'react';
import { ThemeProvider } from 'react-native-elements';
import './config/firebase';
import RootNavigation from './navigation';
export default function App() {
return (
<ThemeProvider>
<RootNavigation />
</ThemeProvider>
);
}
建立欢迎屏幕
我们的欢迎屏幕看起来很好,但它没有功能。我们需要使用React Native元素中的Button组件,为用户添加一种跳转到登录界面或注册界面的方式。
我们需要添加按钮,这些按钮将导航到堆栈中的不同屏幕。
下面是其中一个按钮的样子的例子。
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { StackScreenProps } from '@react-navigation/stack';
import { Button } from 'react-native-elements';
const WelcomeScreen: React.FC<StackScreenProps<any>> = ({ navigation }) => {
return (
<View style={styles.container}>
<Text>Welcome screen!</Text>
<View style={styles.buttons}>
<Button title="Sign in" buttonStyle={styles.button} onPress={() => navigation.navigate('Sign In')} />
<Button title="Sign up" type="outline" buttonStyle={styles.button} onPress={() => navigation.navigate('Sign Up')} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
buttons: {
flex: 1,
},
button: {
marginTop: 10
}
});
export default WelcomeScreen;
这就是结果。

构建注册屏幕
注册屏幕有点复杂和有趣,因为我们需要整合逻辑来在Firebase上创建一个用户。
我们将开始设计这个屏幕,然后我们将添加逻辑来使它像魔术一样工作。这个屏幕包含两个输入元素,一个是电子邮件,一个是密码。它还有一个注册按钮,并可以在出错时显示一个错误信息。
这里是屏幕设计。
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { Input, Button } from 'react-native-elements';
import { StackScreenProps } from '@react-navigation/stack';
const SignUpScreen: React.FC<StackScreenProps<any>> = ({ navigation }) => {
const [value, setValue] = React.useState({
email: '',
password: '',
error: ''
})
function signUp() {
if (value.email === '' || value.password === '') {
setValue({
...value,
error: 'Email and password are mandatory.'
})
return;
}
setValue({
...value,
error: ''
})
}
return (
<View style={styles.container}>
<Text>Signup screen!</Text>
{!!value.error && <View style={styles.error}><Text>{value.error}</Text></View>}
<View style={styles.controls}>
<Input
placeholder='Email'
containerStyle={styles.control}
value={value.email}
onChangeText={(text) => setValue({ ...value, email: text })}
leftIcon={<Icon
name='envelope'
size={16}
/>}
/>
<Input
placeholder='Password'
containerStyle={styles.control}
value={value.password}
onChangeText={(text) => setValue({ ...value, password: text })}
secureTextEntry={true}
leftIcon={<Icon
name='key'
size={16}
/>}
/>
<Button title="Sign up" buttonStyle={styles.control} onPress={signUp} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
controls: {
flex: 1,
},
control: {
marginTop: 10
},
error: {
marginTop: 10,
padding: 10,
color: '#fff',
backgroundColor: '#D54826FF',
}
});
export default SignUpScreen;
接下来,让我们从代码中开始使用Firebase的认证服务。
我们需要更新我们的firebase.ts 配置文件,因为我们从未指定要使用认证。我们所要做的就是在文件的开头导入 firebase/auth ,就在导入Constants 。该文件的导入部分看起来像这样。
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import 'firebase/auth';
import Constants from 'expo-constants';
接下来,让我们更新一下SignUpScreen 的代码,以利用这个库。
让我们从导入和本地对象开始。每次我们想访问Firebase服务时,我们需要导入它的配置和任何我们想使用的模数。在我们的例子中。
import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
接下来,我们将利用函数createUserWithEmailAndPassword ,在注册过程中在Firebase上创建一个用户。如果成功,我们将把用户送到登录界面。如果不成功,我们将显示一个错误信息和细节。
更新signUp 函数,如下所示。
async function signUp() {
if (value.email === '' || value.password === '') {
setValue({
...value,
error: 'Email and password are mandatory.'
})
return;
}
try {
await createUserWithEmailAndPassword(auth, value.email, value.password);
navigation.navigate('Sign In');
} catch (error) {
setValue({
...value,
error: error.message,
})
}
}
建立签到屏幕
签到屏幕看起来与我们的注册屏幕基本相同。代码有95%是相同的,不同的是我们没有调用createUserWithEmailAndPassword, ,而是调用signInWithEmailAndPassword 。
下面是代码。
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { Input, Button } from 'react-native-elements';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
const SignInScreen = () => {
const [value, setValue] = React.useState({
email: '',
password: '',
error: ''
})
async function signIn() {
if (value.email === '' || value.password === '') {
setValue({
...value,
error: 'Email and password are mandatory.'
})
return;
}
try {
await signInWithEmailAndPassword(auth, value.email, value.password);
} catch (error) {
setValue({
...value,
error: error.message,
})
}
}
return (
<View style={styles.container}>
<Text>Signin screen!</Text>
{!!value.error && <View style={styles.error}><Text>{value.error}</Text></View>}
<View style={styles.controls}>
<Input
placeholder='Email'
containerStyle={styles.control}
value={value.email}
onChangeText={(text) => setValue({ ...value, email: text })}
leftIcon={<Icon
name='envelope'
size={16}
/>}
/>
<Input
placeholder='Password'
containerStyle={styles.control}
value={value.password}
onChangeText={(text) => setValue({ ...value, password: text })}
secureTextEntry={true}
leftIcon={<Icon
name='key'
size={16}
/>}
/>
<Button title="Sign in" buttonStyle={styles.control} onPress={signIn} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
controls: {
flex: 1,
},
control: {
marginTop: 10
},
error: {
marginTop: 10,
padding: 10,
color: '#fff',
backgroundColor: '#D54826FF',
}
});
export default SignInScreen;
建立主屏幕
让我们保持简单,只提供一个注销的方法。
与注册屏幕类似,首先我们将构建屏幕,然后是退出功能。
下面是HomeScreen 组件设计的代码。
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useAuthentication } from '../utils/hooks/useAuthentication';
import { Button } from 'react-native-elements';
export default function HomeScreen() {
const { user } = useAuthentication();
return (
<View style={styles.container}>
<Text>Welcome {user?.email}!</Text>
<Button title="Sign Out" style={styles.button} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
button: {
marginTop: 10
}
});
现在,让我们通过替换下面的按钮代码来编写signOut 的功能。
<Button title="Sign Out" style={styles.button} onPress={() => signOut(auth)} />
就这样了!

结语
将Firebase认证整合到Expo应用中的代码是相当简单的,因为大部分的工作都是在设计屏幕上。有了Firebase,就很容易开始建立安全的应用程序。
今天,我们只是建立了一个最基本的原型,但Firebase提供了更多的认证选项和服务,所以要进一步探索它来建立伟大的产品--它是免费的,可以开始使用谢谢你的阅读!
The postIntegrating Firebase authentication into an Expo mobile appappeared first onLogRocket Blog.