在本教程中,我们将看到如何从头开始向我们的Flutter和Firebase应用程序添加苹果登录。
我们将使用pub.dev上的the_apple_sign_inFlutter插件。
这个插件使得在iOS上支持Apple Sign In成为可能,并在Firebase上将其作为一种认证方法。
然而,请注意,这个插件只支持iOS,不支持Android,而且你只能在运行iOS 13及以上的设备上使用。
如果你想在iOS和Android上都启用苹果登录,你应该使用sign_in_with_apple代替。这将在这两个平台上工作,但整合需要一些服务器端的代码。
一些背景
Apple Sign In是一种新的认证方法,在iOS 13及以上版本中可用。
它非常方便,因为你的iOS用户已经有一个Apple ID,可以用它来登录你的应用程序。
因此,就像你在Android上提供Google Sign In一样,在iOS上提供Apple Sign In是有意义的。
前提条件
- 安装了Xcode 11
- 一个苹果开发者账户
- 一个iOS 13.x设备或模拟器,用一个Apple ID登录。
项目设置
在创建一个新的Flutter项目后,将以下依赖关系添加到您的pubspec.yaml 文件。
dependencies:
the_apple_sign_in: ^1.1.1
cupertino_icons: ^1.0.3
firebase_auth: ^3.1.1
firebase_core: ^1.6.0
provider: ^6.0.0
注意:我们将使用Provider进行依赖性注入,但如果你愿意,也可以使用其他东西。
接下来,我们需要将Firebase添加到我们的Flutter应用中。按照这个指南来做。
在我们完成所需的步骤后,GoogleService-Info.plist 文件应该被添加到我们的Xcode项目中。
而在Xcode 11中,选择签名和能力标签,并添加 "Sign In With Apple "作为一个新的能力。
添加Sign In With Apple功能
一旦完成这些,确保在代码签名部分选择一个团队。
设置代码签名选项
这将在苹果开发者门户的 "证书、标识符和配置文件 "部分生成和配置一个应用程序ID。如果你不这样做,签到就无法进行。
作为最后一步,我们需要在Firebase中启用苹果登录。这可以在Authentication ->Sign-in method 下完成。
这样就完成了Apple Sign In的设置,我们就可以深入研究代码了。
检查Apple Sign In是否可用
在我们添加UI代码之前,让我们写一个简单的类来检查苹果登录是否可用*(记住,我们只支持iOS 13+*)。
import 'package:apple_sign_in/apple_sign_in.dart';
class AppleSignInAvailable {
AppleSignInAvailable(this.isAvailable);
final bool isAvailable;
static Future<AppleSignInAvailable> check() async {
return AppleSignInAvailable(await AppleSignIn.isAvailable());
}
}
然后,在我们的main.dart 文件中,让我们修改入口点。
void main() async {
// Fix for: Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final appleSignInAvailable = await AppleSignInAvailable.check();
runApp(Provider<AppleSignInAvailable>.value(
value: appleSignInAvailable,
child: MyApp(),
));
}
第一行防止在绑定初始化之前,如果我们试图在平台通道上发送消息,就会出现异常。这是有必要的,因为我们在运行应用程序之前调用Firebase.initializeApp() 。
然后,我们通过使用我们刚刚创建的类来检查苹果登录是否可用。
然后我们使用Provider来使这个值对我们应用程序中的所有部件可用。
注意:这个检查是提前完成的,这样
appleSignInAvailable,整个widget树都可以同步使用。这样我们就不需要一个FutureBuilder来在我们的widget中执行这个检查。
添加UI代码
取代默认的计数器应用程序,我们想显示一个带有按钮的登录页面。
import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:apple_sign_in_firebase_flutter/apple_sign_in_available.dart';
import 'package:apple_sign_in_firebase_flutter/auth_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SignInPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appleSignInAvailable =
Provider.of<AppleSignInAvailable>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: Text('Sign In'),
),
body: Padding(
padding: const EdgeInsets.all(6.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (appleSignInAvailable.isAvailable)
AppleSignInButton(
style: ButtonStyle.black, // style as needed
type: ButtonType.signIn, // style as needed
onPressed: () {},
),
],
),
),
);
}
}
注意:我们使用collection-if来显示
AppleSignInButton,如果苹果登录是可用的。请看这个视频,了解Dart中的UI-as-code操作。
回到我们的main.dart 文件,我们可以更新我们的根部件来使用SignInPage 。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Apple Sign In with Firebase',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: SignInPage(),
);
}
}
在这个阶段,我们可以在iOS 13模拟器上运行该应用,得到以下结果。
苹果登录按钮
添加认证代码
这里是我们将用于与苹果公司签到的完整认证服务(下面解释)。
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/services.dart';
import 'package:the_apple_sign_in/the_apple_sign_in.dart';
class AuthService {
final _firebaseAuth = FirebaseAuth.instance;
Future<User> signInWithApple({List<Scope> scopes = const []}) async {
// 1. perform the sign-in request
final result = await TheAppleSignIn.performRequests(
[AppleIdRequest(requestedScopes: scopes)]);
// 2. check the result
switch (result.status) {
case AuthorizationStatus.authorized:
final appleIdCredential = result.credential!;
final oAuthProvider = OAuthProvider('apple.com');
final credential = oAuthProvider.credential(
idToken: String.fromCharCodes(appleIdCredential.identityToken!),
accessToken:
String.fromCharCodes(appleIdCredential.authorizationCode!),
);
final userCredential =
await _firebaseAuth.signInWithCredential(credential);
final firebaseUser = userCredential.user!;
if (scopes.contains(Scope.fullName)) {
final fullName = appleIdCredential.fullName;
if (fullName != null &&
fullName.givenName != null &&
fullName.familyName != null) {
final displayName = '${fullName.givenName} ${fullName.familyName}';
await firebaseUser.updateDisplayName(displayName);
}
}
return firebaseUser;
case AuthorizationStatus.error:
throw PlatformException(
code: 'ERROR_AUTHORIZATION_DENIED',
message: result.error.toString(),
);
case AuthorizationStatus.cancelled:
throw PlatformException(
code: 'ERROR_ABORTED_BY_USER',
message: 'Sign in aborted by user',
);
default:
throw UnimplementedError();
}
}
}
首先,我们向我们的方法传递一个List<Scope> 参数。范围是可以从用户那里请求的联系信息的种类(email 和fullName)。
然后,我们对AppleSignIn.performRequests 和await 进行调用以获得结果。
最后,我们用switch 语句来解析结果。三个可能的情况是:authorized,error 和cancelled 。
已授权
如果请求被授权,我们用收到的identityToken 和authorizationCode 创建一个OAuthProvider 证书。
然后我们将其传递给_firebaseAuth.signInWithCredential() ,并得到一个UserCredential ,我们可以用它来提取FirebaseUser 。
而如果我们要求提供全名,我们可以用Apple ID凭证中的fullName ,更新FirebaseUser 对象的资料信息。
注意:根据这个问题,苹果公司的签到API在初次登录后总是返回
null,以获取fullName数据。之后,返回的唯一信息是uid。因此,上面的代码在更新Firebase显示名称之前有一些防御性的null检查。
错误或取消
如果认证失败或被用户取消,我们会抛出一个PlatformException ,可以由调用网站处理。
使用认证代码
现在我们的认证服务已经准备好了,我们可以像这样通过Provider 把它添加到我们的应用程序中。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<AuthService>(
create: (_) => AuthService(),
child: MaterialApp(
title: 'Apple Sign In with Firebase',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: SignInPage(),
),
);
}
}
然后,在我们的SignInPage ,我们可以添加一个方法来签到和处理任何错误。
Future<void> _signInWithApple(BuildContext context) async {
try {
final authService = Provider.of<AuthService>(context, listen: false);
final user = await authService.signInWithApple(
scopes: [Scope.email, Scope.fullName]);
print('uid: ${user.uid}');
} catch (e) {
// TODO: Show alert here
print(e);
}
}
最后,我们记得要在AppleSignInButton 的回调中调用这个方法。
AppleSignInButton(
style: ButtonStyle.black,
type: ButtonType.signIn,
onPressed: () => _signInWithApple(context),
)
测试事情
我们的实现已经完成,我们可以运行这个应用程序。
如果我们按下登录按钮,而我们的模拟器或设备上没有配置Apple ID,我们将得到以下结果。
苹果登录设置提示
用我们的苹果ID登录后,我们可以再次尝试,我们会得到这样的结果。
继续之后,我们会被提示输入我们的苹果ID的密码(或者使用触摸ID/面容ID,如果设备上启用的话)。如果我们要求全名和电子邮件访问,用户将有机会编辑名字,并选择分享或隐藏电子邮件。
在确认了这一点之后,签到就完成了,应用程序就通过了Firebase的认证。
注意:如果认证后签到界面没有被驳回,很可能是因为你忘记在Xcode的代码签名选项中设置团队。
下一个合乎逻辑的步骤是脱离SignInPage ,转而显示主页。这可以通过在SignInPage 上面添加一个小部件来实现,根据onAuthStateChaged 流的FirebaseAuth 来决定显示哪一个页面。如何实现这个目标,请看这个教程。
恭喜你,你现在已经在你的Flutter应用程序中启用了苹果登录功能!你的iOS用户非常感激。你的iOS用户很感激。🙏
本教程的完整源代码在GitHub上。
编码愉快!