在前面的文章中,我们已经看到了如何用以下方法创建一个简单的认证流程 firebase_auth和Provider包创建一个简单的认证流程。
这些技术是我使用Flutter和Firebase的参考认证流程的基础。
我们现在将看到如何使用服务类来封装第三方库和API,并将它们与应用程序的其他部分解耦。我们将使用认证作为一个具体的例子。
TL;DR:
- 把服务类写成一个API封装器,隐藏任何实现细节。
- 这包括API方法及其所有的输入和输出(返回)参数。
- (可选的)为服务类创建一个基础抽象类,这样就可以更容易地用不同的实现来交换这个类了
问题陈述
在上一篇文章中,我们用这段代码用FirebaseAuth 来登录用户。
class SignInPage extends StatelessWidget {
Future<void> _signInAnonymously() async {
try {
// retrieve firebaseAuth from above in the widget tree
final firebaseAuth = Provider.of<FirebaseAuth>(context);
await firebaseAuth.signInAnonymously();
} catch (e) {
print(e); // TODO: show dialog with error
}
}
...
}
在这里,我们用Provider.of<FirebaseAuth>(context) 来检索一个FirebaseAuth 的实例。
这就避免了全局访问的通常问题(更多细节见我之前的文章《全局访问与范围访问》)。
然而,我们仍然在我们的代码中直接访问FirebaseAuth API。
这可能会导致一些问题。
- 如何处理未来版本的
FirebaseAuth中的破坏性变化? - 如果我们将来想把
FirebaseAuth与不同的授权提供者交换,怎么办?
无论怎样,我们都必须在我们的代码库中更新或替换对FirebaseAuth 的调用。
而一旦我们的项目成长,我们可能会添加很多额外的包。这些可能包括共享偏好、权限、分析、本地认证,仅举几个常见的例子。
随着API的变化,这使得我们维护代码所需的努力成倍增加。
解决方案:创建服务类
一个服务类只是一个包装器。
下面是我们如何创建一个基于FirebaseAuth 的通用认证服务。
class User {
const User({@required this.uid});
final String uid;
}
class FirebaseAuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
// private method to create `User` from `FirebaseUser`
User _userFromFirebase(FirebaseUser user) {
return user == null ? null : User(uid: user.uid);
}
Stream<User> get onAuthStateChanged {
// map all `FirebaseUser` objects to `User`, using the `_userFromFirebase` method
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
Future<User> signInAnonymously() async {
final user = await _firebaseAuth.signInAnonymously();
return _userFromFirebase(user);
}
Future<void> signOut() async {
return _firebaseAuth.signOut();
}
}
在这个例子中,我们创建了一个FirebaseAuthService 类,它实现了我们需要的API方法,这些方法来自FirebaseAuth 。
注意我们是如何创建一个简单的User 类的,我们在FirebaseAuthService 中的所有方法的返回类型中使用该类。
这样,客户端代码就完全不依赖于firebase_auth 包,因为它可以与User 对象一起工作,而不是FirebaseUser 。
有了这样的设置,我们就可以更新我们的顶层小部件,以使用新的服务类。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<FirebaseAuthService>(
builder: (_) => FirebaseAuthService(),
child: MaterialApp(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: LandingPage(),
),
);
}
}
然后,我们可以用Provider.of<FirebaseAuthService>(context) 替换所有对Provider.of<FirebaseAuth>(context) 的调用。
因此,我们所有的客户端代码不再需要导入这一行。
import 'package:firebase_auth/firebase_auth.dart';
这意味着,如果在firebase_auth 中引入任何破坏性的变化,任何编译错误只能出现在我们的FirebaseAuthService 类中。
随着我们添加越来越多的包,这种方法使我们的应用程序更容易维护。
作为一个额外的(也是可选的)步骤,我们也可以定义一个抽象的基类。
abstract class AuthService {
Future<User> signInAnonymously();
Future<void> signOut();
Stream<User> get onAuthStateChanged;
}
class FirebaseAuthService implements AuthService {
...
}
通过这种设置,我们可以在创建和使用我们的Provider ,同时在builder 中创建子类的实例时使用基类类型。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<AuthService>( // base class
builder: (_) => FirebaseAuthService(), // concrete subclass
child: MaterialApp(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: LandingPage(),
),
);
}
}
虽然创建基类是额外的工作。只有当我们知道我们需要同时拥有多个实现的时候,它才是值得的。
如果不是这种情况,我建议只写一个具体的服务类。由于现代IDE使重构任务变得简单,如果需要的话,你可以毫无痛苦地重命名这个类和它的用法。
作为参考,我在我的参考认证流程项目中使用了一个有两个实现的基础AuthService 类。
这样我就可以在运行时在Firebase和模拟认证服务之间进行切换,这对测试和演示很有用。
展示时间
下面是一段视频,以我的参考认证流程为例,展示了所有这些技术的实践。
结论
服务类是在你的应用程序中隐藏第三方代码实现细节的一个好方法。
当你需要在代码库中的多个地方调用一个API方法时,它们会特别有用(分析和日志库就是一个很好的例子)。
一言以蔽之。
- 编写一个服务类作为API包装器,隐藏任何实现细节。
- 这包括API方法及其所有的输入和输出(返回)参数。
- (可选的)为服务类创建一个基础抽象类,这样就可以更容易地用不同的实现来交换。
编码愉快!
源代码
本文的示例代码取自我的《使用Flutter和Firebase的参考认证流程》。
反过来,这个项目补充了我的Flutter & Firebase课程中的所有深入材料。