You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Flutter基于Firebase实现带手机号OTP验证的邮箱密码注册登录功能开发求助

Hey there! Let's walk through how to build this enhanced registration and login flow with Firebase in your Flutter app—exactly what you're asking for. I'll break it down into clear, actionable steps with code snippets to make it straightforward:

1. Setup & Dependencies

First, ensure you have the necessary packages in your pubspec.yaml (you might already have some for email/password auth, but add the missing ones):

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.15.0
  firebase_auth: ^4.10.0
  cloud_firestore: ^4.8.3
  # Optional: For a clean OTP input UI, use this package
  flutter_otp_text_field: ^1.1.3

Run flutter pub get after updating, and confirm Firebase is initialized in your main function (you've probably done this already, but just in case):

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}
2. Registration Flow Implementation

Your registration has two core phases: collecting user details, then verifying the phone number via OTP.

Step 2.1: Collect Details & Create Initial User

Build a UI to gather email, password, and phone number (remind users to include the country code, e.g., +1234567890). When the user taps "Register", execute this code:

final auth = FirebaseAuth.instance;
final firestore = FirebaseFirestore.instance;

// Get values from text controllers
final email = emailController.text.trim();
final password = passwordController.text.trim();
final phoneNumber = phoneController.text.trim();

try {
  // Create the user with email/password first
  final userCredential = await auth.createUserWithEmailAndPassword(
    email: email,
    password: password,
  );
  final userId = userCredential.user!.uid;

  // Store user data in Firestore (mark phone as unverified initially)
  await firestore.collection('users').doc(userId).set({
    'email': email,
    'phoneNumber': phoneNumber,
    'phoneVerified': false,
    'createdAt': FieldValue.serverTimestamp(),
  });

  // Send OTP and navigate to verification screen
  await sendOtp(phoneNumber);
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => OtpVerificationScreen(userId: userId, phone: phoneNumber)
    )
  );
} on FirebaseAuthException catch (e) {
  // Handle errors like duplicate email or weak password
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!)));
}

Step 2.2: Send OTP to the Phone Number

Implement the sendOtp function to trigger Firebase's phone authentication:

String verificationId = ''; // Store this globally or pass via state management

Future<void> sendOtp(String phoneNumber) async {
  final auth = FirebaseAuth.instance;

  await auth.verifyPhoneNumber(
    phoneNumber: phoneNumber,
    verificationCompleted: (PhoneAuthCredential credential) async {
      // Auto-verification (rare case, handle it anyway)
      await verifyOtpAndCompleteRegistration(credential, userId);
    },
    verificationFailed: (FirebaseAuthException e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!)));
    },
    codeSent: (String id, int? resendToken) {
      verificationId = id; // Save this to use for OTP verification
    },
    codeAutoRetrievalTimeout: (String id) {
      verificationId = id;
    },
    timeout: const Duration(seconds: 60),
  );
}

Step 2.3: Verify OTP & Finalize Registration

On the OTP screen, when the user enters their code, verify it and mark their phone as verified:

Future<void> verifyOtp(String otpCode, String userId) async {
  final auth = FirebaseAuth.instance;
  final firestore = FirebaseFirestore.instance;

  try {
    final credential = PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: otpCode,
    );

    // Verify the OTP
    await auth.signInWithCredential(credential);

    // Update Firestore to mark phone as verified
    await firestore.collection('users').doc(userId).update({
      'phoneVerified': true,
    });

    // Optional: Link the phone number to the user's auth account
    await auth.currentUser!.updatePhoneNumber(credential);

    // Registration complete! Navigate to login or home screen
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Phone verified successfully!')));
    Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => LoginScreen()));
  } on FirebaseAuthException catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!)));
  }
}
3. Login Flow Modification

Update your existing login logic to check if the user's phone is verified before granting access:

Future<void> login(String email, String password) async {
  final auth = FirebaseAuth.instance;
  final firestore = FirebaseFirestore.instance;

  try {
    final userCredential = await auth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
    final userId = userCredential.user!.uid;

    // Check phone verification status from Firestore
    final userDoc = await firestore.collection('users').doc(userId).get();
    final isPhoneVerified = userDoc.data()?['phoneVerified'] ?? false;

    if (isPhoneVerified) {
      // Allow access to home screen
      Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => HomeScreen()));
    } else {
      // Log out and prompt verification
      await auth.signOut();
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please verify your phone number first!')));
      // Navigate back to OTP verification to re-send code
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (_) => OtpVerificationScreen(userId: userId, phone: userDoc.data()!['phoneNumber'])
        )
      );
    }
  } on FirebaseAuthException catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message!)));
  }
}
4. Key Best Practices
  • Phone Number Format: Always enforce country codes to avoid verification failures.
  • Firestore Security Rules: Restrict user document access to only the owner:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /users/{userId} {
          allow read, write: if request.auth != null && request.auth.uid == userId;
        }
      }
    }
    
  • Error Handling: Cover all edge cases like expired OTPs, invalid phone numbers, and too many requests to keep users informed.
  • State Management: For larger apps, use Provider/Riverpod/Bloc instead of global variables to pass verification IDs and user IDs between screens.

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

火山引擎 最新活动