Flutter电话认证插件firebase_phone_auth_handler_flave的使用

Flutter电话认证插件firebase_phone_auth_handler_flave的使用

pub package likes popularity pub points

  • 一个易于使用的Firebase电话认证包,用于发送和验证一次性密码(OTP),支持自动获取OTP。
  • 该包在网页端支持OTP。

截图

截图1 截图2 截图3 截图4 截图5 截图6 截图7

入门指南

步骤1: 在将Firebase添加到您的应用之前,您需要创建一个Firebase项目来连接您的应用。 访问Firebase项目简介了解更多信息。

步骤2: 若要在您的应用中使用Firebase,您需要将您的应用注册到Firebase项目中。 注册应用通常称为“将您的应用添加到项目”。

如果在网页上使用,请也注册一个网页应用。按照屏幕上的指示初始化项目。

这里添加最新版本的’firebase-auth’ CDN。 (测试版本为8.6.1)

步骤3: 添加Firebase配置文件和SDK(google-services)。

步骤4: 当基本设置完成后,打开控制台并转到项目,然后从左侧菜单选择Authentication

步骤5: 点击Sign-in method旁边的Users选项卡,并启用Phone

步骤6: 按照平台的附加配置步骤以避免任何错误。

步骤7: 重要提示:不要忘记启用Google Cloud PlatformAndroid Device Verification服务。(确保选择了正确的项目)。

步骤8: 最后,在pubspec.yaml文件中添加firebase_core作为依赖项。 并在main方法中调用Firebase.initializeApp(),如示例所示:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(_MainApp());
}

使用说明

要使用此插件,需要在pubspec.yaml文件中添加firebase_phone_auth_handler作为依赖项。

dependencies:
  flutter:
    sdk: flutter
  firebase_phone_auth_handler:

首先导入该插件。

import 'package:firebase_phone_auth_handler/firebase_phone_auth_handler.dart';

FirebasePhoneAuthProvider包装MaterialApp以启用电话认证。

class _MainApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return FirebasePhoneAuthProvider(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomeScreen(),
      ),
    );
  }
}

现在可以在你的小部件树中添加FirebasePhoneAuthHandler小部件,并传递所有必需的参数开始使用。

FirebasePhoneAuthHandler(
    phoneNumber: "+919876543210",
    builder: (context, controller) {
        return SizedBox.shrink();
    },
),

手机号码是将发送OTP的号码,应按以下格式进行格式化:

+919876543210 - 其中+91是国家代码,9876543210是电话号码。

builder返回的小部件将在屏幕上渲染。builder暴露了一个controller,其中包含各种变量和方法。

可以传递回调函数,例如onLoginSuccessonLoginFailed

onLoginSuccess在成功发送OTP且自动验证或手动通过调用verifyOTP函数验证时被调用。回调暴露了UserCredential对象,可以用来查找用户UID和其他信息。布尔值表示是否自动验证了OTP(true)还是手动验证(false)。

onLoginFailed在发送OTP或验证OTP时发生错误或任何内部错误时被调用。回调暴露了FirebaseAuthException,可以用来处理错误。

onCodeSent在OTP成功发送到电话号码时被调用。

FirebasePhoneAuthHandler(
    phoneNumber: "+919876543210",
    // 如果为true,则在成功验证OTP后会注销当前用户。
    signOutOnSuccessfulVerification: false,
    
    linkWithExistingUser: false,
    builder: (context, controller) {
      return SizedBox.shrink();
    },
    onLoginSuccess: (userCredential, autoVerified) {
      debugPrint("autoVerified: $autoVerified");
      debugPrint("Login success UID: ${userCredential.user?.uid}");
    },
    onLoginFailed: (authException, stackTrace) {
      debugPrint("An error occurred: ${authException.message}");
    },
    onError: (error, stackTrace) {},
),

要注销当前用户(如果有),只需调用:

await FirebasePhoneAuthHandler.signOut(context);

如果需要在同一屏幕中注销当前用户,也可以使用controller.signOut()

网页(reCAPTCHA)

默认情况下,reCAPTCHA小部件是一个完全托管的流程,为您的网页应用提供安全性。当触发登录流程时,小部件将呈现为不可见的小部件。一个“不可见”的小部件将作为全屏模态窗口出现在您的应用之上,如下面所示:

reCAPTCHA1

但是,可以传递一个RecaptchaVerifier实例,用于管理小部件。

使用FirebasePhoneAuthHandler中的recaptchaVerifierForWebProvider函数,该函数提供一个布尔值来检查当前平台是否为Web。

注意:如果不是Web平台,不要传递RecaptchaVerifier实例,否则会发生错误。

示例:

recaptchaVerifierForWebProvider: (isWeb) {
    if (isWeb) return RecaptchaVerifier();
},

还可以显示一个内联小部件,用户必须明确按下以验证自己。

reCAPTCHA2

要添加内联小部件,指定RecaptchaVerifier实例的容器参数的DOM元素ID。元素必须存在且为空,否则将抛出错误。如果没有提供容器参数,则小部件将呈现为“不可见”。

RecaptchaVerifier(
  container: 'recaptcha',
  size: RecaptchaVerifierSize.compact,
  theme: RecaptchaVerifierTheme.dark,
  onSuccess: () => print('reCAPTCHA Completed!'),
  onError: (FirebaseAuthException error) => print(error),
  onExpired: () => print('reCAPTCHA Expired!'),
),

如果在身份验证完成后reCAPTCHA徽章未自动消失,尝试在onLoginSuccess中添加以下代码,以便在登录过程完成后消失。

首先从dart:html导入querySelector

import 'dart:html' show querySelector;

然后在onLoginSuccess回调中添加以下代码。

final captcha = querySelector('#__ff-recaptcha-container');
if (captcha != null) captcha.hidden = true;

如果您想完全禁用reCAPTCHA徽章(通常出现在右下角),请在web/index.html中添加以下CSS样式,位于其他标签之外。

<style>
    .grecaptcha-badge { visibility: hidden; }
</style>

我通常如何使用它

我通常有一个电话号码输入字段,用于处理电话号码输入。然后将电话号码传递给VerifyPhoneNumberScreen小部件,来自示例应用。

// 可能有一些UI或对话框来获取电话号码
final phoneNumber = _getPhoneNumber();

// 然后调用
void _verifyPhoneNumber() async {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => VerifyPhoneNumberScreen(phoneNumber: phoneNumber),
    ),
  );
}

/// 在 [VerifyPhoneNumberScreen] 的 onLoginSuccess 回调中路由到主屏幕或其他地方

示例使用

import 'package:firebase_phone_auth_handler/firebase_phone_auth_handler.dart';
import 'package:flutter/material.dart';
import 'package:phone_auth_handler_demo/screens/home_screen.dart';
import 'package:phone_auth_handler_demo/utils/helpers.dart';
import 'package:phone_auth_handler_demo/widgets/custom_loader.dart';
import 'package:phone_auth_handler_demo/widgets/pin_input_field.dart';

class VerifyPhoneNumberScreen extends StatefulWidget {
  static const id = 'VerifyPhoneNumberScreen';

  final String phoneNumber;

  const VerifyPhoneNumberScreen({
    Key? key,
    required this.phoneNumber,
  }) : super(key: key);

  [@override](/user/override)
  State<VerifyPhoneNumberScreen> createState() => _VerifyPhoneNumberScreenState();
}

class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen> with WidgetsBindingObserver {
  bool isKeyboardVisible = false;

  late final ScrollController scrollController;

  [@override](/user/override)
  void initState() {
    scrollController = ScrollController();
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  [@override](/user/override)
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    scrollController.dispose();
    super.dispose();
  }

  [@override](/user/override)
  void didChangeMetrics() {
    final bottomViewInsets = WidgetsBinding.instance.window.viewInsets.bottom;
    isKeyboardVisible = bottomViewInsets > 0;
  }

  // 当pin输入字段获得焦点时滚动到底部
  Future<void> _scrollToBottomOnKeyboardOpen() async {
    while (!isKeyboardVisible) {
      await Future.delayed(const Duration(milliseconds: 50));
    }

    await Future.delayed(const Duration(milliseconds: 250));

    await scrollController.animateTo(
      scrollController.position.maxScrollExtent,
      duration: const Duration(milliseconds: 250),
      curve: Curves.easeIn,
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SafeArea(
      child: FirebasePhoneAuthHandler(
        phoneNumber: widget.phoneNumber,
        signOutOnSuccessfulVerification: false,
        linkWithExistingUser: false,
        autoRetrievalTimeOutDuration: const Duration(seconds: 60),
        otpExpirationDuration: const Duration(seconds: 60),
        onCodeSent: () {
          log(VerifyPhoneNumberScreen.id, msg: 'OTP sent!');
        },
        onLoginSuccess: (userCredential, autoVerified) async {
          log(
            VerifyPhoneNumberScreen.id,
            msg: autoVerified
                ? 'OTP was fetched automatically!'
                : 'OTP was verified manually!',
          );

          showSnackBar('Phone number verified successfully!');

          log(
            VerifyPhoneNumberScreen.id,
            msg: 'Login Success UID: ${userCredential.user?.uid}',
          );

          Navigator.pushNamedAndRemoveUntil(
            context,
            HomeScreen.id,
            (route) => false,
          );
        },
        onLoginFailed: (authException, stackTrace) {
          log(
            VerifyPhoneNumberScreen.id,
            msg: authException.message,
            error: authException,
            stackTrace: stackTrace,
          );

          switch (authException.code) {
            case 'invalid-phone-number':
              // 无效的电话号码
              return showSnackBar('Invalid phone number!');
            case 'invalid-verification-code':
              // 输入的OTP无效
              return showSnackBar('The entered OTP is invalid!');
            // 处理其他错误代码
            default:
              showSnackBar('Something went wrong!');
            // 进一步处理错误
          }
        },
        onError: (error, stackTrace) {
          log(
            VerifyPhoneNumberScreen.id,
            error: error,
            stackTrace: stackTrace,
          );

          showSnackBar('An error occurred!');
        },
        builder: (context, controller) {
          return Scaffold(
            appBar: AppBar(
              leadingWidth: 0,
              leading: const SizedBox.shrink(),
              title: const Text('Verify Phone Number'),
              actions: [
                if (controller.codeSent)
                  TextButton(
                    onPressed: controller.isOtpExpired
                        ? () async {
                            log(VerifyPhoneNumberScreen.id, msg: 'Resend OTP');
                            await controller.sendOTP();
                          }
                        : null,
                    child: Text(
                      controller.isOtpExpired
                          ? 'Resend'
                          : '${controller.otpExpirationTimeLeft.inSeconds}s',
                      style: const TextStyle(color: Colors.blue, fontSize: 18),
                    ),
                  ),
                const SizedBox(width: 5),
              ],
            ),
            body: controller.isSendingCode
                ? Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: const [
                      CustomLoader(),
                      SizedBox(height: 50),
                      Center(
                        child: Text(
                          'Sending OTP',
                          style: TextStyle(fontSize: 25),
                        ),
                      ),
                    ],
                  )
                : ListView(
                    padding: const EdgeInsets.all(20),
                    controller: scrollController,
                    children: [
                      Text(
                        "We've sent an SMS with a verification code to ${widget.phoneNumber}",
                        style: const TextStyle(fontSize: 25),
                      ),
                      const SizedBox(height: 10),
                      const Divider(),
                      if (controller.isListeningForOtpAutoRetrieve)
                        Column(
                          children: const [
                            CustomLoader(),
                            SizedBox(height: 50),
                            Text(
                              'Listening for OTP',
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                fontSize: 25,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                            SizedBox(height: 15),
                            Divider(),
                            Text('OR', textAlign: TextAlign.center),
                            Divider(),
                          ],
                        ),
                      const SizedBox(height: 15),
                      const Text(
                        'Enter OTP',
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      const SizedBox(height: 15),
                      PinInputField(
                        length: 6,
                        onFocusChange: (hasFocus) async {
                          if (hasFocus) await _scrollToBottomOnKeyboardOpen();
                        },
                        onSubmit: (enteredOtp) async {
                          final verified = await controller.verifyOtp(enteredOtp);
                          if (verified) {
                            // 验证成功
                            // 将调用 onLoginSuccess 处理程序
                          } else {
                            // 验证失败
                            // 将调用 onLoginFailed 或 onError 回调,并传递错误
                          }
                        },
                      ),
                    ],
                  ),
          );
        },
      ),
    );
  }
}

更多关于Flutter电话认证插件firebase_phone_auth_handler_flave的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter电话认证插件firebase_phone_auth_handler_flave的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


firebase_phone_auth_handler_flave 是一个用于在 Flutter 应用中实现 Firebase 电话号码认证的插件。它简化了与 Firebase 电话号码认证的集成过程,使你能够轻松地在应用中实现电话号码认证功能。

以下是如何在 Flutter 项目中使用 firebase_phone_auth_handler_flave 插件的步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 firebase_phone_auth_handler_flave 插件的依赖。

dependencies:
  flutter:
    sdk: flutter
  firebase_phone_auth_handler_flave: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 配置 Firebase

在使用 Firebase 电话号码认证之前,你需要在 Firebase 控制台中配置你的项目。

  1. 登录 Firebase 控制台
  2. 创建一个新的 Firebase 项目(如果还没有)。
  3. 在 Firebase 控制台中,导航到 Authentication 部分,并启用 电话 作为登录方法。
  4. 按照 Firebase 的指示配置你的 Android 和 iOS 应用(如果需要)。

3. 初始化 Firebase

在你的 Flutter 项目中,确保你已经初始化了 Firebase。通常,你可以在 main.dart 文件中进行初始化。

import 'package:firebase_core/firebase_core.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

4. 使用 firebase_phone_auth_handler_flave

接下来,你可以在你的应用中使用 firebase_phone_auth_handler_flave 插件来实现电话号码认证。

import 'package:flutter/material.dart';
import 'package:firebase_phone_auth_handler_flave/firebase_phone_auth_handler_flave.dart';

class PhoneAuthScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Phone Authentication'),
      ),
      body: Center(
        child: FirebasePhoneAuthHandler(
          onCodeSent: (verificationId) {
            // 当验证码发送时调用
            print('Verification code sent: $verificationId');
          },
          onVerificationFailed: (error) {
            // 当验证失败时调用
            print('Verification failed: $error');
          },
          onVerificationCompleted: (credential) {
            // 当验证成功时调用
            print('Verification completed: $credential');
          },
          onCodeAutoRetrievalTimeout: (verificationId) {
            // 当自动获取验证码超时时调用
            print('Auto retrieval timeout: $verificationId');
          },
          phoneNumber: '+1234567890', // 替换为用户的电话号码
        ),
      ),
    );
  }
}

5. 处理验证码

onCodeSent 回调中,你可以处理验证码的输入。通常,你需要显示一个输入框,让用户输入收到的验证码,然后使用 PhoneAuthProvider.credential 来验证验证码。

FirebasePhoneAuthHandler(
  onCodeSent: (verificationId) {
    // 显示验证码输入框
    showDialog(
      context: context,
      builder: (context) {
        String smsCode = '';
        return AlertDialog(
          title: Text('Enter Verification Code'),
          content: TextField(
            onChanged: (value) {
              smsCode = value;
            },
            decoration: InputDecoration(hintText: 'Enter code'),
          ),
          actions: [
            TextButton(
              onPressed: () async {
                final credential = PhoneAuthProvider.credential(
                  verificationId: verificationId,
                  smsCode: smsCode,
                );
                await FirebaseAuth.instance.signInWithCredential(credential);
                Navigator.pop(context);
              },
              child: Text('Submit'),
            ),
          ],
        );
      },
    );
  },
  // 其他回调...
);

6. 处理认证结果

onVerificationCompleted 回调中,你可以处理认证成功后的逻辑,例如导航到主屏幕或显示欢迎消息。

FirebasePhoneAuthHandler(
  onVerificationCompleted: (credential) async {
    await FirebaseAuth.instance.signInWithCredential(credential);
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(builder: (context) => HomeScreen()),
    );
  },
  // 其他回调...
);

7. 处理错误

onVerificationFailed 回调中,你可以处理认证失败的情况,例如显示错误消息。

FirebasePhoneAuthHandler(
  onVerificationFailed: (error) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Verification failed: $error')),
    );
  },
  // 其他回调...
);
回到顶部