You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo集成Firebase电话认证遇NOBRIDGE错误:RNFBAppModule缺失

问题:Expo React Native集成Firebase电话认证时RNFBAppModule未找到错误

错误信息

ERROR  Error: Native module RNFBAppModule not found. Re-check module install, linking, configuration, build and install steps.

环境信息

Expo SDK:"~52.0.23",
"react": "18.3.1",
"react-native": "0.76.5",
"@react-native-firebase/app": "^21.6.1",
"@react-native-firebase/auth": "^21.6.1",
Platform: iOS/Android

已尝试操作

  • 执行命令安装Firebase:npx expo install @react-native-firebase/app
  • 在app.json中添加配置:
{
"expo": {
"plugins": [
"@react-native-firebase/app"
]
}
}
  • 配置firebase-config.js并填入正确凭据

登录页面代码

import React, { useState } from 'react';
import { 
  View, Text, TextInput, TouchableOpacity, SafeAreaView, 
  StyleSheet, KeyboardAvoidingView, Platform, Keyboard, 
  TouchableWithoutFeedback, Alert
} from 'react-native';
import { Phone, Package, ArrowRight } from 'lucide-react-native';
import { useUser } from './context/UserContext';
import { auth } from '../firebase.config';
import { PhoneAuthProvider, signInWithCredential } from 'firebase/auth';

const LoginScreen = () => {
  const [phoneNumber, setPhoneNumber] = useState('');
  const [otpSent, setOtpSent] = useState(false);
  const [otp, setOtp] = useState('');
  const [error, setError] = useState('');
  const [verificationId, setVerificationId] = useState(null);
  const { updateUserData } = useUser();

  const handleSendOTP = async () => {
    try {
      const phoneRegex = /^[0-9]{10}$/;
      if (!phoneRegex.test(phoneNumber)) {
        setError('请输入有效的10位手机号码');
        return;
      }

      const formattedPhone = `+91${phoneNumber}`; // 调整国家代码
      
      const appVerifier = {
        type: 'recaptcha',
        verify: () => Promise.resolve('dummy-token')
      };

      const verificationId = await PhoneAuthProvider.verifyPhoneNumber(
        auth,
        formattedPhone,
        appVerifier
      );
      
      setVerificationId(verificationId);
      setOtpSent(true);
      setError('');
      Alert.alert('OTP已发送', '请查看手机获取验证码');
    } catch (err) {
      setError('发送OTP失败,请重试。');
      console.error('Send OTP error:', err);
    }
  };

  const handleVerifyOTP = async () => {
    try {
      if (otp.length !== 6) {
        setError('请输入6位OTP验证码');
        return;
      }

      const credential = PhoneAuthProvider.credential(verificationId, otp);
      const userCredential = await signInWithCredential(auth, credential);
      
      updateUserData({
        phone: phoneNumber,
        isLoggedIn: true,
        uid: userCredential.user.uid
      });
      
      setError('');
    } catch (err) {
      setError('OTP验证码无效,请重试。');
      console.error('Verify OTP error:', err);
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <KeyboardAvoidingView 
          behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
          style={styles.keyboardContainer}
        >
          <View style={styles.header}></View>
          <View style={styles.content}>
            <View style={styles.logoContainer}>
              <Package color="#FF85A2" size={64} />
              <Text style={styles.welcomeText}>欢迎使用Pink Parcel</Text>
              <Text style={styles.subtitleText}>您的优质配送伙伴</Text>
            </View>

            {!otpSent ? (
              <View style={styles.inputSection}>
                <View style={styles.inputContainer}>
                  <Phone color="#FF85A2" size={24} style={styles.inputIcon} />
                  <TextInput
                    placeholder="输入手机号码"
                    placeholderTextColor="#8F90A6"
                    keyboardType="phone-pad"
                    value={phoneNumber}
                    onChangeText={setPhoneNumber}
                    maxLength={10}
                    style={styles.input}
                  />
                </View>

                <TouchableOpacity 
                  style={styles.primaryButton} 
                  onPress={handleSendOTP}
                >
                  <Text style={styles.buttonText}>发送OTP</Text>
                  <ArrowRight color="#FFFFFF" size={20} />
                </TouchableOpacity>
              </View>
            ) : (
              <View style={styles.inputSection}>
                <View style={styles.inputContainer}>
                  <Package color="#E91E63" size={24} style={styles.inputIcon} />
                  <TextInput
                    placeholder="输入6位OTP验证码"
                    placeholderTextColor="#8F90A6"
                    keyboardType="number-pad"
                    value={otp}
                    onChangeText={setOtp}
                    maxLength={6}
                    style={styles.input}
                  />
                </View>

                <TouchableOpacity 
                  style={styles.primaryButton}
                  onPress={handleVerifyOTP}
                >
                  <Text style={styles.buttonText}>验证OTP</Text>
                  <ArrowRight color="#FFFFFF" size={20} />
                </TouchableOpacity>

                <TouchableOpacity 
                  style={styles.secondaryButton}
                  onPress={() => setOtpSent(false)}
                >
                  <Text style={styles.secondaryButtonText}>更换手机号码</Text>
                </TouchableOpacity>
              </View>
            )}

            {error ? (
              <Text style={styles.errorText}>{error}</Text>
            ) : null}
          </View>
        </KeyboardAvoidingView>
      </TouchableWithoutFeedback>
    </SafeAreaView>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFF5F8',
  },
  keyboardContainer: {
    flex: 1,
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    paddingHorizontal: 20,
    paddingVertical: 60,
    backgroundColor: '#FFF5F8',
    borderBottomWidth: 1,
    borderBottomColor: '#FFF5F8',
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#2D2D3A',
  },
  content: {
    flex: 1,
    padding: 20,
  },
  logoContainer: {
    alignItems: 'center',
    marginVertical: 40,
  },
  welcomeText: {
    fontSize: 24,
    fontWeight: '700',
    color: '#2D2D3A',
    marginTop: 16,
    marginBottom: 8,
  },
  subtitleText: {
    fontSize: 16,
    color: '#8F90A6',
  },
  inputSection: {
    backgroundColor: '#FFFFFF',
    borderRadius: 24,
    padding: 20,
    shadowColor: '#E91E63',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.08,
    shadowRadius: 12,
    elevation: 5,
  },
  inputContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#FFE4EC',
    marginBottom: 20,
    paddingVertical: 8,
  },
  inputIcon: {
    marginRight: 12,
  },
  input: {
    flex: 1,
    height: 50,
    fontSize: 16,
    color: '#2D2D3A',
  },
  primaryButton: {
    backgroundColor: '#E91E63',
    borderRadius: 16,
    paddingVertical: 14,
    alignItems: 'center',
    flexDirection: 'row',
    justifyContent: 'center',
    gap: 8,
  },
  buttonText: {
    color: '#FFFFFF',
    fontSize: 15,
    fontWeight: '600',
  },
  secondaryButton: {
    paddingVertical: 14,
    alignItems: 'center',
    marginTop: 12,
  },
  secondaryButtonText: {
    color: '#E91E63',
    fontSize: 15,
    fontWeight: '600',
  },
  errorText: {
    color: '#E91E63',
    textAlign: 'center',
    marginTop: 16,
    fontSize: 14,
  },
});

export default LoginScreen;

预期与实际行为

  • 预期:Firebase电话认证功能正常运行
  • 实际:出现NOBRIDGE错误,提示原生模块未找到

解决方案

1. 适配Expo工作流

@react-native-firebase依赖原生模块,Expo管理工作流默认不支持第三方原生模块,需转为裸工作流:

npx expo prebuild

预构建后会生成iOS/Android原生目录,后续需用Xcode/Android Studio构建项目,而非直接使用expo start。

2. 完善Firebase插件配置

app.json中需补充Firebase项目凭据,仅添加插件名无法完成原生配置:

{
  "expo": {
    "plugins": [
      [
        "@react-native-firebase/app",
        {
          "googleServicesFile": "./google-services.json", // Android配置文件
          "googleServicesPlist": "./GoogleService-Info.plist" // iOS配置文件
        }
      ],
      "@react-native-firebase/auth"
    ]
  }
}

确保google-services.json和GoogleService-Info.plist已放置在项目根目录,文件从Firebase控制台下载。

3. 清理缓存并重新安装依赖

  • 删除依赖目录和锁文件:
rm -rf node_modules package-lock.json yarn.lock
  • 重新安装依赖:
npm install
  • 清除Expo缓存:
npx expo start --clear

4. 确认认证模块安装

电话认证需单独安装auth模块:

npx expo install @react-native-firebase/auth

安装后确保插件已添加到app.json的plugins数组中。

5. 修复App Verifier配置

代码中的dummy verifier仅适用于测试,生产环境需配置真实的Recaptcha:

  • 导入RecaptchaVerifier:
import { RecaptchaVerifier } from '@react-native-firebase/auth';
  • 在组件中初始化Recaptcha:
useEffect(() => {
  const appVerifier = new RecaptchaVerifier('recaptcha-container', {}, auth);
  setAppVerifier(appVerifier);
}, []);
  • 在界面中添加空View用于渲染Recaptcha:
<View id="recaptcha-container" />

内容的提问来源于stack exchange,提问作者Ramanjit Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:40:54