Как определить GoRouter, зависящий от провайдера?
Я интегрирую GoRouter в свое приложение Flutter, где уже использую Riverpod. у меня есть
isAuthorizedProvider
определяется следующим образом:
final isAuthorizedProvider = Provider<bool>((ref) {
final authStateChanged = ref.watch(_authStateChangedProvider);
final user = authStateChanged.asData?.value;
return user != null;
});
И я не уверен, как определить 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,
);
}
Это правильный способ сделать это?
2 ответа
Я не думаю, что вы должны звонить по этой линии
ref.watch(isAuthorizedProvider);
внутри блока перенаправления, потому что это приведет к тому, что весь ваш
GoRouter
instance для перестроения (и вы потеряете весь стек навигации).
Вот как я это сделал:
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. Таким образом, в сочетании с этим вам необходимо выполнить явное событие навигации при входе/выходе.
В качестве альтернативы вы можете использовать
refreshListenable
аргумент.
Вы можете сделать это таким образом, используя перенаправление, однако я придумал способ, который использует . Таким образом, вы сохраняете исходное состояние навигатора (вы будете перенаправлены обратно на ту страницу, которую вы изначально посетили в Интернете или с прямой ссылкой), и весь маршрутизатор не нужно постоянно перестраивать.
final routerProvider = Provider((ref) {
return GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const OrdersScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const AuthScreen(),
),
],
navigatorBuilder: (context, state, child) {
return Consumer(
builder: (_, ref, __) =>
ref.watch(authControllerProvider).asData?.value == null
? Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => AuthScreen(),
),
)
: child,
);
},
);
});
В моем примере
Если вы хотите перенаправить пользователей на основе ролей (например, только администратор может видеть панель администратора), то эта логика должна войти в функцию перенаправления с использованием слушаемого, как описано @bizz84.