Flutter:用服务类设计认证API的方法

148 阅读5分钟

在前面的文章中,我们已经看到了如何用以下方法创建一个简单的认证流程 firebase_authProvider包创建一个简单的认证流程。

这些技术是我使用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课程中的所有深入材料。