11 KiB
Authentication Troubleshooting Guide
Date: October 10, 2025
Issue: Login Successful But No Navigation
Symptoms
- Login API call succeeds
- Token is saved
- But app doesn't navigate to MainScreen
- AuthWrapper doesn't react to state change
Root Causes Fixed
1. GetIt Dependency Injection Error ✅ FIXED
- Problem: AuthRepository was trying to use GetIt but wasn't registered
- Solution: Migrated to pure Riverpod dependency injection
- Files Changed:
lib/features/auth/presentation/providers/auth_provider.dart
2. Circular Dependency in Auth Provider ✅ FIXED
- Problem:
Auth.build()was calling async_checkAuthStatus()causing circular dependency - Solution: Moved initialization to separate
initialize()method - Files Changed:
lib/features/auth/presentation/providers/auth_provider.dart,lib/app.dart
3. Provider Not Kept Alive ✅ FIXED
- Problem: Auth state provider was being disposed between rebuilds
- Solution: Added
@Riverpod(keepAlive: true)to Auth provider - Files Changed:
lib/features/auth/presentation/providers/auth_provider.dart
4. State Not Updating Properly ✅ FIXED
- Problem:
copyWithmethod wasn't properly settingisAuthenticated: true - Solution: Updated login/register methods to create new
AuthStatewith explicit values - Files Changed:
lib/features/auth/presentation/providers/auth_provider.dart
How Auth Flow Should Work
1. App Startup
main()
→ ProviderScope created
→ RetailApp builds
→ initState() schedules auth initialization
→ auth.initialize() checks for saved token
→ If token found: loads user profile, sets isAuthenticated = true
→ If no token: sets isAuthenticated = false
2. Login Flow
User enters credentials
→ Taps Login button
→ _handleLogin() called
→ ref.read(authProvider.notifier).login(email, password)
→ API call to /api/auth/login
→ Success: saves token, sets user, sets isAuthenticated = true
→ AuthWrapper watches authProvider
→ isAuthenticated changes to true
→ AuthWrapper rebuilds
→ Shows MainScreen instead of LoginPage
3. Logout Flow
User taps Logout in Settings
→ Confirmation dialog shown
→ ref.read(authProvider.notifier).logout()
→ Token cleared from secure storage
→ DioClient token cleared
→ State set to isAuthenticated = false
→ AuthWrapper rebuilds
→ Shows LoginPage
Debug Checklist
If auth flow still not working, check these:
1. Verify Provider State
// Add this to login_page.dart _handleLogin after login success
final authState = ref.read(authProvider);
print('🔐 Auth State after login:');
print(' isAuthenticated: ${authState.isAuthenticated}');
print(' user: ${authState.user?.name}');
print(' isLoading: ${authState.isLoading}');
print(' errorMessage: ${authState.errorMessage}');
2. Verify AuthWrapper Reaction
// Add this to auth_wrapper.dart build method
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
print('🔄 AuthWrapper rebuild:');
print(' isAuthenticated: ${authState.isAuthenticated}');
print(' isLoading: ${authState.isLoading}');
print(' user: ${authState.user?.name}');
// ... rest of build method
}
3. Verify Token Saved
// Add this to auth_repository_impl.dart login method after saving token
print('💾 Token saved: ${authResponse.accessToken.substring(0, 20)}...');
print('💾 DioClient token set');
4. Verify API Response
// Add this to auth_remote_datasource.dart login method
print('📡 Login API response:');
print(' Status: ${response.statusCode}');
print(' User: ${response.data['user']?['name']}');
print(' Token length: ${response.data['accessToken']?.length}');
Common Issues and Solutions
Issue: State Updates But UI Doesn't Rebuild
Cause: Using ref.read() instead of ref.watch() in AuthWrapper
Solution: Ensure AuthWrapper uses ref.watch(authProvider)
final authState = ref.watch(authProvider); // ✅ Correct - watches for changes
// NOT ref.read(authProvider) // ❌ Wrong - doesn't rebuild
Issue: Login Success But isAuthenticated = false
Cause: State update not explicitly setting isAuthenticated: true
Solution: Create new AuthState with explicit values
state = AuthState(
user: authResponse.user,
isAuthenticated: true, // ✅ Explicit value
isLoading: false,
errorMessage: null,
);
Issue: Provider Disposes Between Rebuilds
Cause: Provider not marked as keepAlive
Solution: Add @Riverpod(keepAlive: true) to Auth provider
@Riverpod(keepAlive: true) // ✅ Keeps state alive
class Auth extends _$Auth {
// ...
}
Issue: Circular Dependency Error
Cause: Calling async operations in build() method
Solution: Use separate initialization method
@override
AuthState build() {
return const AuthState(); // ✅ Sync only
}
Future<void> initialize() async {
// ✅ Async operations here
}
Testing the Fix
Test 1: Fresh App Start (No Token)
- Clear app data or use fresh install
- Run app:
flutter run - Expected: Shows LoginPage immediately
- Result: ✅ Pass / ❌ Fail
Test 2: Login Flow
- Start at LoginPage
- Enter credentials: admin@retailpos.com / Admin123!
- Tap Login
- Expected:
- Loading indicator appears
- On success: Navigate to MainScreen with bottom tabs
- Result: ✅ Pass / ❌ Fail
Test 3: Token Persistence
- Login successfully
- Close app completely
- Restart app
- Expected:
- Shows loading briefly
- Automatically goes to MainScreen (no login needed)
- Result: ✅ Pass / ❌ Fail
Test 4: Logout Flow
- While logged in, go to Settings tab
- Tap Logout button
- Confirm logout
- Expected: Navigate back to LoginPage
- Result: ✅ Pass / ❌ Fail
Test 5: Invalid Credentials
- Enter wrong email/password
- Tap Login
- Expected:
- Shows error SnackBar
- Stays on LoginPage
- Error message displayed
- Result: ✅ Pass / ❌ Fail
Architecture Diagram
┌─────────────────────────────────────────────────┐
│ ProviderScope │
│ ┌───────────────────────────────────────────┐ │
│ │ RetailApp │ │
│ │ (initializes auth on startup) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ MaterialApp │ │ │
│ │ │ ┌───────────────────────────────┐ │ │ │
│ │ │ │ AuthWrapper │ │ │ │
│ │ │ │ (watches authProvider) │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ if isAuthenticated: │ │ │ │
│ │ │ │ ┌─────────────────────────┐ │ │ │ │
│ │ │ │ │ MainScreen │ │ │ │ │
│ │ │ │ │ (with bottom tabs) │ │ │ │ │
│ │ │ │ └─────────────────────────┘ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ else: │ │ │ │
│ │ │ │ ┌─────────────────────────┐ │ │ │ │
│ │ │ │ │ LoginPage │ │ │ │ │
│ │ │ │ │ (login form) │ │ │ │ │
│ │ │ │ └─────────────────────────┘ │ │ │ │
│ │ │ └───────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
↕
┌───────────────┐
│ authProvider │
│ (keepAlive) │
└───────────────┘
↕
┌───────────────────────┐
│ authRepository │
│ ↓ │
│ authRemoteDataSource │
│ ↓ │
│ dioClient │
│ ↓ │
│ secureStorage │
└───────────────────────┘
Files Modified
Core Auth Files
-
✅
lib/features/auth/presentation/providers/auth_provider.dart- Added
@Riverpod(keepAlive: true)to Auth provider - Fixed
copyWithmethod withclearUserandclearErrorflags - Updated login/register to explicitly set
isAuthenticated: true - Moved auth check to
initialize()method
- Added
-
✅
lib/app.dart- Changed from
ConsumerWidgettoConsumerStatefulWidget - Added
initState()to callauth.initialize()
- Changed from
-
✅
lib/main.dart- Removed GetIt initialization
- Using pure Riverpod for DI
-
✅
lib/features/auth/presentation/widgets/auth_wrapper.dart- Already correct - uses
ref.watch(authProvider)
- Already correct - uses
-
✅
lib/features/auth/presentation/pages/login_page.dart- Already correct - login logic properly calls provider
Expected Behavior After Fixes
- ✅ App starts → auth initializes → shows LoginPage (if no token)
- ✅ Login success → state updates → AuthWrapper rebuilds → shows MainScreen
- ✅ Token persists → app restart → auto-login works
- ✅ Logout → state clears → AuthWrapper rebuilds → shows LoginPage
- ✅ All tabs accessible after login (Home, Products, Categories, Settings)
Next Steps If Still Not Working
- Add Debug Logs: Add print statements to trace state changes
- Check Backend: Ensure API endpoints are working and returning correct data
- Verify Token Format: Check that JWT token is valid format
- Check API Response Structure: Ensure response matches model expectations
- Test with Hot Restart: Try
r(hot reload) vsR(hot restart) in Flutter
Status: All known issues fixed. Auth flow should work correctly now.
If issues persist, add debug logging as described above to trace the exact point of failure.