f
This commit is contained in:
324
AUTH_TROUBLESHOOTING.md
Normal file
324
AUTH_TROUBLESHOOTING.md
Normal file
@@ -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<void> 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.
|
||||||
@@ -60,18 +60,20 @@ class AuthState {
|
|||||||
bool? isAuthenticated,
|
bool? isAuthenticated,
|
||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
|
bool clearUser = false,
|
||||||
|
bool clearError = false,
|
||||||
}) {
|
}) {
|
||||||
return AuthState(
|
return AuthState(
|
||||||
user: user ?? this.user,
|
user: clearUser ? null : (user ?? this.user),
|
||||||
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auth state notifier provider
|
/// Auth state notifier provider
|
||||||
@riverpod
|
@Riverpod(keepAlive: true)
|
||||||
class Auth extends _$Auth {
|
class Auth extends _$Auth {
|
||||||
@override
|
@override
|
||||||
AuthState build() {
|
AuthState build() {
|
||||||
@@ -119,13 +121,14 @@ class Auth extends _$Auth {
|
|||||||
required String email,
|
required String email,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
state = state.copyWith(isLoading: true, errorMessage: null);
|
state = state.copyWith(isLoading: true, clearError: true);
|
||||||
|
|
||||||
final result = await _repository.login(email: email, password: password);
|
final result = await _repository.login(email: email, password: password);
|
||||||
|
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) {
|
(failure) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
isAuthenticated: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: failure.message,
|
errorMessage: failure.message,
|
||||||
);
|
);
|
||||||
@@ -150,7 +153,7 @@ class Auth extends _$Auth {
|
|||||||
required String password,
|
required String password,
|
||||||
List<String> roles = const ['user'],
|
List<String> roles = const ['user'],
|
||||||
}) async {
|
}) async {
|
||||||
state = state.copyWith(isLoading: true, errorMessage: null);
|
state = state.copyWith(isLoading: true, clearError: true);
|
||||||
|
|
||||||
final result = await _repository.register(
|
final result = await _repository.register(
|
||||||
name: name,
|
name: name,
|
||||||
@@ -162,6 +165,7 @@ class Auth extends _$Auth {
|
|||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) {
|
(failure) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
isAuthenticated: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: failure.message,
|
errorMessage: failure.message,
|
||||||
);
|
);
|
||||||
@@ -181,7 +185,7 @@ class Auth extends _$Auth {
|
|||||||
|
|
||||||
/// Get user profile (refresh user data)
|
/// Get user profile (refresh user data)
|
||||||
Future<void> getProfile() async {
|
Future<void> getProfile() async {
|
||||||
state = state.copyWith(isLoading: true, errorMessage: null);
|
state = state.copyWith(isLoading: true, clearError: true);
|
||||||
|
|
||||||
final result = await _repository.getProfile();
|
final result = await _repository.getProfile();
|
||||||
|
|
||||||
@@ -195,6 +199,7 @@ class Auth extends _$Auth {
|
|||||||
(user) {
|
(user) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
user: user,
|
user: user,
|
||||||
|
isAuthenticated: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ final class AuthProvider extends $NotifierProvider<Auth, AuthState> {
|
|||||||
argument: null,
|
argument: null,
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'authProvider',
|
name: r'authProvider',
|
||||||
isAutoDispose: true,
|
isAutoDispose: false,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
$allTransitiveDependencies: null,
|
$allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
@@ -234,7 +234,7 @@ final class AuthProvider extends $NotifierProvider<Auth, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$authHash() => r'67ba3b381308cce5e693827ad22db940840c3978';
|
String _$authHash() => r'4b053a7691f573316a8957577dd27a3ed73d89be';
|
||||||
|
|
||||||
/// Auth state notifier provider
|
/// Auth state notifier provider
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user