使用 go_router 和 flutter_bloc 以及 Auth 和 UnAuth 流程进行反应式重定向

Tah*_*lam 6 flutter flutter-navigation flutter-bloc flutter-go-router

使用 flutter_bloc 和 go_router 实现基于身份验证更改的导航。

我需要做的是,如果用户经过身份验证,则应将其重定向到主屏幕,并能够导航身份验证流程...(HomeScreen,TestScreen1,TestScreen2)

如果用户在任何时候未经身份验证,则应将其重定向到登录屏幕,如果未知,则应重定向到启动屏幕。

我已经解决了许多 stackoverflow 问题和 github 问题,但没有一个提供我们如何实现这一目标的最佳实践。

问题:

  • 无法在身份验证流程中导航,HomeScreen -> Test1 || 主屏幕 -> 测试 2

提供以下资产:

Tried to listen to a value exposed with provider, from outside of the widget tree.

This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.

To fix, write:
Provider.of<$T>(context, listen: false);

It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.

The context used was: $context
Run Code Online (Sandbox Code Playgroud)
  • 使用 flutter_web 时,如果我在主屏幕 (/auth/home_screen) 上并将 URL 手动更改为 /login_screen ,则会引发异常:
======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for GoRouteInformationProvider:
Tried to listen to a value exposed with provider, from outside of the widget tree.

This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
Run Code Online (Sandbox Code Playgroud)

改为context.readwatch不是我真正喜欢的,因为那样我就无法听取集团的变化。

到目前为止我尝试过的是:

我的应用程序路由器:

class AppRouter {
  GoRouter get router => _goRouter;

  late final GoRouter _goRouter = GoRouter(
    routes: <GoRoute>[
      GoRoute(
        path: '/',
        name: 'SPLASH',
        builder: (context, state) => const SplashScreen(),
      ),
      GoRoute(
        path: '/auth',
        name: 'AUTH',
        builder: (context, state) => HomeScreen(),
        routes: <GoRoute>[
          GoRoute(
            path: 'home_screen',
            name: 'HOME',
            builder: (context, state) => HomeScreen(),
          ),
          GoRoute(
            path: 'test_screen_1',
            name: 'TEST_SCREEN_1',
            builder: (context, state) => TESTSCREEN1(),
          ),
          GoRoute(
            path: 'test_screen_2',
            name: 'TEST_SCREEN_2',
            builder: (context, state) => TESTSCREEN2(),
          ),
        ],
      ),
      GoRoute(
        path: '/login_screen',
        name: 'LOGIN',
        builder: (context, state) => LoginScreen(),
      ),
    ],
    redirect: (context, state) {
      final authState = context.watch<AuthBloc>().state;
      final loginLocation = state.namedLocation('LOGIN');
      final authFlow = state.namedLocation('AUTH');
      final splashLocation = state.namedLocation('SPLASH');

      if (authState is AuthStateAuthenticated) {
        return authFlow;
      } else if (authState is AuthStateUnAuthenticated) {
        return loginLocation;
      } else {
        return splashLocation;
      }
    },
  );
}

Run Code Online (Sandbox Code Playgroud)

应用程序:

class App extends StatelessWidget {

  AppRouter appRouter = AppRouter();
  @override
  Widget build(BuildContext context) {
    return RepositoryProvider<AuthService>(
      create: (context) => AuthService(),
      child: MultiBlocProvider(
        providers: [
          BlocProvider<AuthBloc>(create: (context) => AuthBloc(authService:    RepositoryProvider.of<AuthService>(context))),
                  ],
        child: MaterialApp.router(
          routerDelegate: appRouter.router.routerDelegate,
          routeInformationParser: appRouter.router.routeInformationParser,
          routeInformationProvider: appRouter.router.routeInformationProvider,
        ),
      ),
    );
  }
}

Run Code Online (Sandbox Code Playgroud)

Asa*_*saf 2

我正在使用 flutter_bloc 和 get_it 进行依赖注入,并且也在这个主题上苦苦挣扎(主要是由于缺乏适当的文档)。

最终我的解决方案基于官方文档中的这个示例。

步骤 1:在 auth_bloc.dart 文件中添加 AuthStreamScope 和 AuthStream 类。
注意:在下面的代码中,替换getIt.get<AuthBloc>()为您的 auth bloc 实例:

class AuthStreamScope extends InheritedNotifier<AuthStream> {
  AuthStreamScope({super.key, required super.child}) : super(notifier: AuthStream(getIt.get<AuthBloc>().stream));
  static AuthStream of(BuildContext ctx) => ctx.dependOnInheritedWidgetOfExactType<AuthStreamScope>()!.notifier!;
}

class AuthStream extends ChangeNotifier {
  late final StreamSubscription<dynamic> _subscription;

  AuthStream(Stream<dynamic> stream) {
    notifyListeners();
    _subscription = stream.asBroadcastStream().listen((_) => notifyListeners());
  }

  bool isSignedIn() => getIt.get<AuthBloc>().state.user != null;

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }
}
Run Code Online (Sandbox Code Playgroud)

第 2 步:使用 AuthStreamScope 包装您的应用程序:

// from with your app's build method:
return AuthStreamScope(
    child: MaterialApp.router(
      theme: theme,
      darkTheme: darkTheme,
      routerConfig: _routerInstance, // your GoRouter
    ),
  ),
);
Run Code Online (Sandbox Code Playgroud)

第三步:在你的GoRouter初始化中,监听auth的变化,如下所示。

GoRouter(
  redirect: (BuildContext context, GoRouterState state) async {
    // calling `of` method creates a dependency of AuthStreamScope. It will make go_router to reparse
    // current route if AuthStream has new sign-in information.
    final bool loggedIn = AuthStreamScope.of(context).isSignedIn();

    // implement your redirection logic here

    return null; // no need to redirect
  },

  // routes, etc.
)
Run Code Online (Sandbox Code Playgroud)

因此本质上 GoRouter 正在侦听任何 AuthBloc 流更新并相应地重定向。特别是 Auth,与其他块不同,可以定义为(惰性)单例,因此 GetIt 非常方便,如上面的代码所示。

我希望它有帮助。