Loading...
Loading...
Debug Flutter bottom tab stuck-loading, auth redirect loops, and reload/route restoration bugs in Riverpod + GoRouter apps. Use when tabs show infinite spinner, wrong tab after reload, or splash screen ignores current route.
npx skill4agent add hoangsoft90/ai_skills flutter-tab-auth-debug/account/attendancesession == nullcheckAuth()Splash auth status changedunknown → authenticated → ?unknowncheckAuth()verifySession()authenticated/attendanceapp.dartauthStatus == unknownnullnullsession == nullFuture<void> checkAuth() async {
// ... read token, baseUrl ...
try {
final result = await client
.verifySession(token)
.timeout(const Duration(seconds: 10));
if (result != null) {
final sessionData = result['session'] as Map<String, dynamic>?;
if (sessionData != null) {
final session = Session.fromJson(sessionData);
ref.read(sessionProvider.notifier).set(session);
state = AuthStatus.authenticated;
return;
}
}
} on TimeoutException {
// server treo → coi unauthenticated
} catch (_) {}
state = AuthStatus.unauthenticated;
}ref.listen<AuthStatus>(authStatusProvider, (previous, next) {
if (!mounted) return;
if (next == AuthStatus.authenticated) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
final location = GoRouter.of(context).state.matchedLocation;
if (location == '/' || location == '/login') {
context.go('/attendance');
}
// Otherwise stay on current route
}
});
} else if (next == AuthStatus.unauthenticated) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) context.go('/login');
});
}
});redirect: (context, state) {
final authStatus = ref.read(authStatusProvider);
if (authStatus == AuthStatus.unknown) {
final path = state.matchedLocation;
if (path == '/' || path == '/login') return null;
return '/';
}
// ... authenticated / unauthenticated branches
}if (session == null) {
final authStatus = ref.watch(authStatusProvider);
if (authStatus == AuthStatus.unauthenticated) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) context.go('/login');
});
return Scaffold(body: Center(child: Text('Phiên hết hạn')));
}
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}Reload page at /account
├─ app.dart: authStatus == unknown → redirect → /
├─ SplashScreen renders
├─ checkAuth() gọi verifySession (có timeout 10s)
├─ AuthStatus → authenticated
├─ SplashScreen: location == '/' → go('/attendance')
└─ User manually navigate sang /account → OK
Reload page at /account (fix state A/B/C applied)
├─ app.dart: authStatus == unknown → redirect → /
├─ SplashScreen renders
├─ checkAuth() gọi verifySession (có timeout 10s)
├─ AuthStatus → authenticated
├─ SplashScreen: location == '/' → go('/attendance')
└─ User manually navigate sang /account → OK| Lỗi | Nguyên nhân | Fix |
|---|---|---|
| go_router ^17 không có getter | Dùng |
SplashScreen chuyển về | Hardcode | Kiểm tra |
| AccountScreen loading vô hạn sau reload | | Redirect về |
| Không có timeout trên verifySession | |
.edits/fix1.mdlib/app.dartlib/screens/splash_screen.dartlib/providers/providers.dart