当已经在路由上时,如何使用 go_router 和 Firebase 身份验证在 Flutter 中处理身份验证?

问题描述 投票:0回答:2

在我的 Flutter 应用程序中,我使用

go_router
包来管理路由,并使用
Firebase Authentication
进行用户身份验证。我有几个屏幕需要用户经过身份验证才能访问,例如帐户管理、交易和详细信息。

目前,我已经使用

go_router
实现了重定向,以确保未经身份验证的用户被正确重定向。但是,当用户已经位于这些屏幕之一并注销或会话过期时,我面临着挑战。

我正在考虑使用 BlocListener 来检测每个屏幕上身份验证状态的变化,但这似乎会导致代码重复。由于 Firebase 身份验证,我还有一个 Stream 通知身份验证状态的更改,并更新

contex.isUserSignIn
变量。

使用 go_router 和 Firebase 身份验证有效处理 Flutter 中的注销或会话过期事件的最佳实践是什么?

flutter firebase-authentication flutter-navigation flutter-go-router gorouter
2个回答
7
投票

我一直在努力解决完全相同的问题,并研究了几种替代方案和选项。

TL;DR: 选项 3 是我的首选,它使用

GoRouter.refresh()
级别的
main()
方法根据来自 auth 流的事件动态更新 GoRouter 状态。

选项 1:遵循 go_router async_redirection.dart 示例

请参阅此处的示例:https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/async_redirection.dart

这将顶层应用程序,通常

MyApp
(由
main()
中的
runApp()
返回)包装在 InheritedNotifer 小部件中,他们称之为
StreamAuthScope
,并在通知器
StreamAuthNotifier
和 go_router 的解析管道之间创建依赖关系。当身份验证状态发生变化时(如
MyApp
通过
App
所传达的),这将依次重建
StreamAuthNotifier
(或示例中的
notifyListeners()
)。

我基于 Provider 包实现了一个类似的模型,其中

ChangeProviderNotifier
替换了
StreamAuthScope
并包装了
MyApp
返回的顶级
main()
。然而,这不允许在
Provider.of<>
外壳内创建受监控的
GoRouter( redirect: )
。为了解决这个问题,我创建了一个传入
getRouter
isUserSignIn
函数,该函数由
Provider.of<>
主体中但在
MyApp
函数之前的
build
进行监控。这可行,但感觉很麻烦 导致每次身份验证状态更改时都会重建主
MyApp
。如果需要,我相信您可以用 BLoC 模型代替 Provider 做类似的事情。

选项 2:使用
GoRouter
refreshListenable:
参数

这是基于此 go_router redirection.dart 示例:https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart

在您提到的问题中,您有一个通知身份验证状态更改的流。您可以使用

extends ChangeNotifier
将其包装在类中以使其可监听。然后,在构造函数中,您可以使用
.listen
实例化和监视流,并在每次身份验证状态更改时(可能每次出现流事件时)在附件中发出
notifyListerners()
。在我的例子中,我将这个类称为
AuthNotifier
然后可以将其用作可听的,并使用
GoRouter
refreshListenable:
参数,简单地如下:
refreshListenable: AuthNotifier()

示例

AuthNotifier

class AuthNotifier extends ChangeNotifier {
  AuthNotifier() {
    // Continuously monitor for authStateChanges
    // per: https://firebase.google.com/docs/auth/flutter/start#authstatechanges
    _subscription =
        FirebaseAuth.instance.authStateChanges().listen((User? user) {
          // if user != null there is a user logged in, however
          // we can just monitor for auth state change and notify
          notifyListeners();
        });
  } // End AuthNotifier constructor

  late final StreamSubscription<dynamic> _subscription;

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }
}

注意:为了避免创建和监视多个流,您需要确保此构造函数仅在您的应用程序中调用一次(在本例中作为

GoRouter
refreshListenable:
的一部分),或者将其修改为单例.

选项3:使用
GoRouter
.refresh()
方法

与选项 2 类似但更直接的方法是使用

GoRouter
.refresh()
方法。这直接调用内部
notifyListerners()
来刷新GoRouter配置。我们可以使用与上面的
AuthNotifier
类似的类,但我们不需要
extends ChangeNotifier
,并且会调用
router.refresh()
来代替
notifyListeners()
,其中
router
是您的
GoRouter()
配置。这个新类将在
main()
中实例化。

鉴于其如此简单(2-3行代码),我们也可以跳过类的定义和实例化,直接在

main()
主体中实现功能,如下:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  // Listen for Auth changes and .refresh the GoRouter [router]
  FirebaseAuth.instance.authStateChanges().listen((User? user) {
    router.refresh();
  });

  runApp(
    const MyApp(),
  );
}

由于这似乎是最直接、最简单的解决方案,因此它是我首选的解决方案,也是我已经实施的解决方案。然而,那里有很多令人困惑和过时的信息,我觉得我没有足够的经验来声称它是任何类型的“最佳实践”,所以将其留给其他人来判断和评论。

我希望所有这些对您和其他人有所帮助,因为我花了很长时间来制定这些不同的选项,并涉猎了各种各样的材料和选项。我觉得绝对有机会改进这方面的官方 go_router 文档!


0
投票

为了详细说明 Arik_E 的答案,这是我的实现:

我的 main.dart 文件:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  // Listen for Auth changes and .refresh the GoRouter [router]
  GoRouter router = RoutingService().router;
  FirebaseAuth.instance.authStateChanges().listen((User? user) {
    router.refresh();
  });

  runApp(App(router: router));
}

class App extends StatelessWidget {
  const App({super.key, required this.router});
  final GoRouter router;

  @override
  Widget build(BuildContext context) => MaterialApp.router(
        routerConfig: router,
      );
}

我有一个包含我的路由器的类:

class RoutingService {
  final router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
        path: MainMenuPage.route,
        builder: (BuildContext context, GoRouterState state) =>
            const MainMenuPage(),
      ),
      GoRoute(
        path: LoginPage.route,
        builder: (BuildContext context, GoRouterState state) =>
            const LoginPage(),
      ),
    ],

    // redirect to the login page if the user is not logged in
    redirect: (BuildContext context, GoRouterState state) async {
      final bool loggedIn = FirebaseAuth.instance.currentUser != null;
      final bool loggingIn = state.matchedLocation == LoginPage.route;
      if (!loggedIn) return LoginPage.route;
      if (loggingIn) return MainMenuPage.route;
      // no need to redirect at all
      return null;
    },
  );
}

在我的 MainMenuPage 和 LoginPage 中,我定义了路由,以便我可以重用它。只需将

static const route = '/yourRouteName'
添加到小部件即可。

© www.soinside.com 2019 - 2024. All rights reserved.