如何定义依赖于 Provider 的 GoRouter?

How to define a GoRouter that depends on a Provider?

我正在将 GoRouter 集成到我已经在使用 Riverpod 的 Flutter 应用程序中。我有一个 isAuthorizedProvider 定义如下:

final isAuthorizedProvider = Provider<bool>((ref) {
  final authStateChanged = ref.watch(_authStateChangedProvider);
  final user = authStateChanged.asData?.value;
  return user != null;
});

而且我不确定如何定义一个依赖于上面的 Provider 的 GoRouter。我想出了以下内容:

final goRouterProvider = Provider<GoRouter>((ref) => GoRouter(
      debugLogDiagnostics: true,
      redirect: (state) {
        final isAuthorized = ref.watch(isAuthorizedProvider);
        final isSigningIn = state.subloc == state.namedLocation('sign_in');

        if (!isAuthorized) {
          return isSigningIn ? null : state.namedLocation('sign_in');
        }

        // if the user is logged in but still on the login page, send them to
        // the home page
        if (isSigningIn) return '/';

        // no need to redirect at all
        return null;
      },
      routes: [
        GoRoute(
          path: '/',
          ...,
        ),
        GoRoute(
          name: 'sign_in',
          path: '/sign_in',
          ...,
        ),
        GoRoute(
            name: 'main',
            path: '/main',
            ...,
        ),
        ...
      ],
    ));

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final goRouter = ref.watch(goRouterProvider);
    return MaterialApp.router(
      routeInformationParser: goRouter.routeInformationParser,
      routerDelegate: goRouter.routerDelegate,
    );
  }

这样做正确吗?

我不认为你应该拨打这条线

ref.watch(isAuthorizedProvider);

在重定向块内,因为这会导致整个 GoRouter 实例重建(并且您将丢失整个导航堆栈)。

我是这样做的:

class AppRouterListenable extends ChangeNotifier {
  AppRouterListenable({required this.authRepository}) {
    _authStateSubscription =
        authRepository.authStateChanges().listen((appUser) {
      _isLoggedIn = appUser != null;
      notifyListeners();
    });
  }
  final AuthRepository authRepository;
  late final StreamSubscription<AppUser?> _authStateSubscription;
  var _isLoggedIn = false;
  bool get isLoggedIn => _isLoggedIn;

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

final appRouterListenableProvider =
    ChangeNotifierProvider<AppRouterListenable>((ref) {
  final authRepository = ref.watch(authRepositoryProvider);
  return AppRouterListenable(authRepository: authRepository);
});

final goRouterProvider = Provider<GoRouter>((ref) {
  final authRepository = ref.watch(authRepositoryProvider);
  final appRouterListenable =
      AppRouterListenable(authRepository: authRepository);
  return GoRouter(
    debugLogDiagnostics: false,
    initialLocation: '/',
    redirect: (state) {
      if (appRouterListenable.isLoggedIn) {
        // on login complete, redirect to home
        if (state.location == '/signIn') {
          return '/';
        }
      } else {
        // on logout complete, redirect to home
        if (state.location == '/account') {
          return '/';
        }
        // TODO: Only allow admin pages if user is admin (#125)
        if (state.location.startsWith('/admin') ||
            state.location.startsWith('/orders')) {
          return '/';
        }
      }
      // disallow card payment screen if not on web
      if (!kIsWeb) {
        if (state.location == '/cart/checkout/card') {
          return '/cart/checkout';
        }
      }
      return null;
    },
    routes: [],
  );
}

请注意,此代码不是反应性的,因为它会在 authState 更改时刷新路由器。所以结合这个,当你 sign-in/sign-out.

时,你需要执行一个显式的导航事件

或者,您可以使用 refreshListenable 参数。