From 10ccd0300d9c6cac3c1090797d8de8ea113cf2d0 Mon Sep 17 00:00:00 2001 From: Phuoc Nguyen Date: Fri, 10 Oct 2025 17:40:53 +0700 Subject: [PATCH] f --- AUTH_TROUBLESHOOTING.md | 324 ++++++++++++++++++ .../presentation/providers/auth_provider.dart | 17 +- .../providers/auth_provider.g.dart | 4 +- 3 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 AUTH_TROUBLESHOOTING.md diff --git a/AUTH_TROUBLESHOOTING.md b/AUTH_TROUBLESHOOTING.md new file mode 100644 index 0000000..ddf504d --- /dev/null +++ b/AUTH_TROUBLESHOOTING.md @@ -0,0 +1,324 @@ +# 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**: `copyWith` method wasn't properly setting `isAuthenticated: true` +- **Solution**: Updated login/register methods to create new `AuthState` with 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 +```dart +// 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 +```dart +// 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 +```dart +// 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 +```dart +// 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)` +```dart +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 +```dart +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 +```dart +@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 +```dart +@override +AuthState build() { + return const AuthState(); // ✅ Sync only +} + +Future initialize() async { + // ✅ Async operations here +} +``` + +--- + +## Testing the Fix + +### Test 1: Fresh App Start (No Token) +1. **Clear app data** or use fresh install +2. **Run app**: `flutter run` +3. **Expected**: Shows LoginPage immediately +4. **Result**: ✅ Pass / ❌ Fail + +### Test 2: Login Flow +1. **Start at LoginPage** +2. **Enter credentials**: admin@retailpos.com / Admin123! +3. **Tap Login** +4. **Expected**: + - Loading indicator appears + - On success: Navigate to MainScreen with bottom tabs +5. **Result**: ✅ Pass / ❌ Fail + +### Test 3: Token Persistence +1. **Login successfully** +2. **Close app completely** +3. **Restart app** +4. **Expected**: + - Shows loading briefly + - Automatically goes to MainScreen (no login needed) +5. **Result**: ✅ Pass / ❌ Fail + +### Test 4: Logout Flow +1. **While logged in, go to Settings tab** +2. **Tap Logout button** +3. **Confirm logout** +4. **Expected**: Navigate back to LoginPage +5. **Result**: ✅ Pass / ❌ Fail + +### Test 5: Invalid Credentials +1. **Enter wrong email/password** +2. **Tap Login** +3. **Expected**: + - Shows error SnackBar + - Stays on LoginPage + - Error message displayed +5. **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 `copyWith` method with `clearUser` and `clearError` flags + - Updated login/register to explicitly set `isAuthenticated: true` + - Moved auth check to `initialize()` method + +- ✅ `lib/app.dart` + - Changed from `ConsumerWidget` to `ConsumerStatefulWidget` + - Added `initState()` to call `auth.initialize()` + +- ✅ `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)` + +- ✅ `lib/features/auth/presentation/pages/login_page.dart` + - Already correct - login logic properly calls provider + +--- + +## Expected Behavior After Fixes + +1. ✅ App starts → auth initializes → shows LoginPage (if no token) +2. ✅ Login success → state updates → AuthWrapper rebuilds → shows MainScreen +3. ✅ Token persists → app restart → auto-login works +4. ✅ Logout → state clears → AuthWrapper rebuilds → shows LoginPage +5. ✅ All tabs accessible after login (Home, Products, Categories, Settings) + +--- + +## Next Steps If Still Not Working + +1. **Add Debug Logs**: Add print statements to trace state changes +2. **Check Backend**: Ensure API endpoints are working and returning correct data +3. **Verify Token Format**: Check that JWT token is valid format +4. **Check API Response Structure**: Ensure response matches model expectations +5. **Test with Hot Restart**: Try `r` (hot reload) vs `R` (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. diff --git a/lib/features/auth/presentation/providers/auth_provider.dart b/lib/features/auth/presentation/providers/auth_provider.dart index 15ed972..7a7d243 100644 --- a/lib/features/auth/presentation/providers/auth_provider.dart +++ b/lib/features/auth/presentation/providers/auth_provider.dart @@ -60,18 +60,20 @@ class AuthState { bool? isAuthenticated, bool? isLoading, String? errorMessage, + bool clearUser = false, + bool clearError = false, }) { return AuthState( - user: user ?? this.user, + user: clearUser ? null : (user ?? this.user), isAuthenticated: isAuthenticated ?? this.isAuthenticated, isLoading: isLoading ?? this.isLoading, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: clearError ? null : (errorMessage ?? this.errorMessage), ); } } /// Auth state notifier provider -@riverpod +@Riverpod(keepAlive: true) class Auth extends _$Auth { @override AuthState build() { @@ -119,13 +121,14 @@ class Auth extends _$Auth { required String email, required String password, }) async { - state = state.copyWith(isLoading: true, errorMessage: null); + state = state.copyWith(isLoading: true, clearError: true); final result = await _repository.login(email: email, password: password); return result.fold( (failure) { state = state.copyWith( + isAuthenticated: false, isLoading: false, errorMessage: failure.message, ); @@ -150,7 +153,7 @@ class Auth extends _$Auth { required String password, List roles = const ['user'], }) async { - state = state.copyWith(isLoading: true, errorMessage: null); + state = state.copyWith(isLoading: true, clearError: true); final result = await _repository.register( name: name, @@ -162,6 +165,7 @@ class Auth extends _$Auth { return result.fold( (failure) { state = state.copyWith( + isAuthenticated: false, isLoading: false, errorMessage: failure.message, ); @@ -181,7 +185,7 @@ class Auth extends _$Auth { /// Get user profile (refresh user data) Future getProfile() async { - state = state.copyWith(isLoading: true, errorMessage: null); + state = state.copyWith(isLoading: true, clearError: true); final result = await _repository.getProfile(); @@ -195,6 +199,7 @@ class Auth extends _$Auth { (user) { state = state.copyWith( user: user, + isAuthenticated: true, isLoading: false, ); }, diff --git a/lib/features/auth/presentation/providers/auth_provider.g.dart b/lib/features/auth/presentation/providers/auth_provider.g.dart index 468dbb4..9ac5ac6 100644 --- a/lib/features/auth/presentation/providers/auth_provider.g.dart +++ b/lib/features/auth/presentation/providers/auth_provider.g.dart @@ -213,7 +213,7 @@ final class AuthProvider extends $NotifierProvider { argument: null, retry: null, name: r'authProvider', - isAutoDispose: true, + isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @@ -234,7 +234,7 @@ final class AuthProvider extends $NotifierProvider { } } -String _$authHash() => r'67ba3b381308cce5e693827ad22db940840c3978'; +String _$authHash() => r'4b053a7691f573316a8957577dd27a3ed73d89be'; /// Auth state notifier provider