fix: prevent GoRouter recreation on auth state change
routerProvider was watching authProvider and returning a new GoRouter on every status transition (unknown → authenticated). This caused MaterialApp.router to rebuild the entire navigation tree, which triggered all data providers to start loading before auth was confirmed. Switch to refreshListenable pattern: GoRouter is created once, _RouterNotifier fires notifyListeners() when auth changes, and GoRouter re-runs redirect using ref.read(authProvider). Add /loading splash route shown during AuthStatus.unknown so no authenticated screen (and no API call) is initiated until the stored-token check completes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,21 +22,38 @@ import '../../features/products/product_provider.dart';
|
|||||||
import '../../shared/models/recipe.dart';
|
import '../../shared/models/recipe.dart';
|
||||||
import '../../shared/models/saved_recipe.dart';
|
import '../../shared/models/saved_recipe.dart';
|
||||||
|
|
||||||
|
// Notifies GoRouter when auth state changes without recreating the router.
|
||||||
|
class _RouterNotifier extends ChangeNotifier {
|
||||||
|
_RouterNotifier(Ref ref) {
|
||||||
|
ref.listen<AuthState>(authProvider, (_, __) => notifyListeners());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final routerProvider = Provider<GoRouter>((ref) {
|
final routerProvider = Provider<GoRouter>((ref) {
|
||||||
final authState = ref.watch(authProvider);
|
final notifier = _RouterNotifier(ref);
|
||||||
|
ref.onDispose(notifier.dispose);
|
||||||
|
|
||||||
return GoRouter(
|
return GoRouter(
|
||||||
initialLocation: '/home',
|
initialLocation: '/loading',
|
||||||
|
refreshListenable: notifier,
|
||||||
redirect: (context, state) {
|
redirect: (context, state) {
|
||||||
|
// Use ref.read — this runs inside GoRouter, not inside a Riverpod build.
|
||||||
|
final authState = ref.read(authProvider);
|
||||||
final isLoggedIn = authState.status == AuthStatus.authenticated;
|
final isLoggedIn = authState.status == AuthStatus.authenticated;
|
||||||
final isAuthRoute = state.matchedLocation.startsWith('/auth');
|
final isAuthRoute = state.matchedLocation.startsWith('/auth');
|
||||||
|
|
||||||
if (authState.status == AuthStatus.unknown) return null;
|
// Show splash until the stored-token check completes.
|
||||||
|
if (authState.status == AuthStatus.unknown) return '/loading';
|
||||||
if (!isLoggedIn && !isAuthRoute) return '/auth/login';
|
if (!isLoggedIn && !isAuthRoute) return '/auth/login';
|
||||||
if (isLoggedIn && isAuthRoute) return '/home';
|
if (isLoggedIn && isAuthRoute) return '/home';
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
|
// Splash shown while auth status is unknown.
|
||||||
|
GoRoute(
|
||||||
|
path: '/loading',
|
||||||
|
builder: (_, __) => const _SplashScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/auth/login',
|
path: '/auth/login',
|
||||||
builder: (_, __) => const LoginScreen(),
|
builder: (_, __) => const LoginScreen(),
|
||||||
@@ -118,6 +135,15 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class _SplashScreen extends StatelessWidget {
|
||||||
|
const _SplashScreen();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class _InvalidRoute extends StatelessWidget {
|
class _InvalidRoute extends StatelessWidget {
|
||||||
const _InvalidRoute();
|
const _InvalidRoute();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user