Files
retail/docs/AUTH_TROUBLESHOOTING.md
2025-10-10 21:49:17 +07:00

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: 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

// 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)

  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
  4. 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.