# GoRouter Navigation Setup - Complete Guide This document explains the complete navigation setup for the warehouse management app using GoRouter with authentication-based redirects. ## Files Created/Modified ### New Files 1. **`/lib/core/router/app_router.dart`** - Main router configuration 2. **`/lib/core/router/README.md`** - Detailed router documentation 3. **`/lib/features/warehouse/presentation/pages/warehouse_selection_page_example.dart`** - Integration examples ### Modified Files 1. **`/lib/main.dart`** - Updated to use new router provider 2. **`/lib/features/operation/presentation/pages/operation_selection_page.dart`** - Updated navigation ## Architecture Overview ### Route Structure ``` /login → LoginPage /warehouses → WarehouseSelectionPage /operations → OperationSelectionPage (requires warehouse) /products → ProductsPage (requires warehouse + operationType) ``` ### Navigation Flow ``` ┌─────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │ Login │ --> │ Warehouses │ --> │ Operations │ --> │ Products │ └─────────┘ └────────────┘ └────────────┘ └──────────┘ │ │ │ │ └─────────────────┴──────────────────┴──────────────────┘ Protected Routes (Require Authentication via SecureStorage) ``` ## Key Features ### 1. Authentication-Based Redirects - **Unauthenticated users** → Redirected to `/login` - **Authenticated users on /login** → Redirected to `/warehouses` - Uses `SecureStorage.isAuthenticated()` to check access token ### 2. Type-Safe Navigation Extension methods provide type-safe navigation: ```dart // Type-safe with auto-completion context.goToOperations(warehouse); context.goToProducts(warehouse: warehouse, operationType: 'import'); // vs. error-prone manual navigation context.go('/operations', extra: warehouse); // Less safe ``` ### 3. Parameter Validation Routes validate required parameters and redirect on error: ```dart final warehouse = state.extra as WarehouseEntity?; if (warehouse == null) { // Show error and redirect to safe page return _ErrorScreen(message: 'Warehouse data is required'); } ``` ### 4. Reactive Navigation Router automatically reacts to authentication state changes: ```dart // Login → Router detects auth change → Redirects to /warehouses await ref.read(authProvider.notifier).login(username, password); // Logout → Router detects auth change → Redirects to /login await ref.read(authProvider.notifier).logout(); ``` ## Usage Guide ### Basic Navigation #### 1. Navigate to Login ```dart context.goToLogin(); ``` #### 2. Navigate to Warehouses ```dart context.goToWarehouses(); ``` #### 3. Navigate to Operations with Warehouse ```dart // From warehouse selection page void onWarehouseSelected(WarehouseEntity warehouse) { context.goToOperations(warehouse); } ``` #### 4. Navigate to Products with Warehouse and Operation ```dart // From operation selection page void onOperationSelected(WarehouseEntity warehouse, String operationType) { context.goToProducts( warehouse: warehouse, operationType: operationType, // 'import' or 'export' ); } ``` ### Complete Integration Example #### Warehouse Selection Page ```dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:minhthu/core/router/app_router.dart'; import 'package:minhthu/features/warehouse/domain/entities/warehouse_entity.dart'; class WarehouseSelectionPage extends ConsumerWidget { const WarehouseSelectionPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // Watch warehouse state final state = ref.watch(warehouseProvider); return Scaffold( appBar: AppBar( title: const Text('Select Warehouse'), actions: [ IconButton( icon: const Icon(Icons.logout), onPressed: () async { // Logout - router will auto-redirect to login await ref.read(authProvider.notifier).logout(); }, ), ], ), body: ListView.builder( itemCount: state.warehouses.length, itemBuilder: (context, index) { final warehouse = state.warehouses[index]; return ListTile( title: Text(warehouse.name), subtitle: Text(warehouse.code), onTap: () { // Type-safe navigation to operations context.goToOperations(warehouse); }, ); }, ), ); } } ``` #### Operation Selection Page ```dart import 'package:flutter/material.dart'; import 'package:minhthu/core/router/app_router.dart'; import 'package:minhthu/features/warehouse/domain/entities/warehouse_entity.dart'; class OperationSelectionPage extends StatelessWidget { final WarehouseEntity warehouse; const OperationSelectionPage({required this.warehouse}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(warehouse.name)), body: Column( children: [ ElevatedButton( onPressed: () { // Navigate to products with import operation context.goToProducts( warehouse: warehouse, operationType: 'import', ); }, child: const Text('Import Products'), ), ElevatedButton( onPressed: () { // Navigate to products with export operation context.goToProducts( warehouse: warehouse, operationType: 'export', ); }, child: const Text('Export Products'), ), ], ), ); } } ``` ## Authentication Integration ### How It Works 1. **App Starts** - Router checks `SecureStorage.isAuthenticated()` - If no token → Redirects to `/login` - If token exists → Allows navigation 2. **User Logs In** ```dart // AuthNotifier saves token and updates state await loginUseCase(request); // Saves to SecureStorage state = AuthState.authenticated(user); // GoRouterRefreshStream detects auth state change ref.listen(authProvider, (_, __) => notifyListeners()); // Router re-evaluates redirect logic // User is now authenticated → Redirects to /warehouses ``` 3. **User Logs Out** ```dart // AuthNotifier clears token and resets state await secureStorage.clearAll(); state = const AuthState.initial(); // Router detects auth state change // User is no longer authenticated → Redirects to /login ``` ### SecureStorage Methods Used ```dart // Check authentication Future isAuthenticated() async { final token = await getAccessToken(); return token != null && token.isNotEmpty; } // Save tokens (during login) Future saveAccessToken(String token); Future saveRefreshToken(String token); // Clear tokens (during logout) Future clearAll(); ``` ## Error Handling ### 1. Missing Route Parameters If required parameters are missing, user sees error and gets redirected: ```dart GoRoute( path: '/operations', builder: (context, state) { final warehouse = state.extra as WarehouseEntity?; if (warehouse == null) { // Show error screen and redirect after frame WidgetsBinding.instance.addPostFrameCallback((_) { context.go('/warehouses'); }); return const _ErrorScreen( message: 'Warehouse data is required', ); } return OperationSelectionPage(warehouse: warehouse); }, ) ``` ### 2. Page Not Found Custom 404 page with navigation back to login: ```dart errorBuilder: (context, state) { return Scaffold( body: Center( child: Column( children: [ Icon(Icons.error_outline, size: 64), Text('Page "${state.uri.path}" does not exist'), ElevatedButton( onPressed: () => context.go('/login'), child: const Text('Go to Login'), ), ], ), ), ); } ``` ### 3. Authentication Errors If `SecureStorage` throws an error, redirect to login for safety: ```dart Future _handleRedirect(context, state) async { try { final isAuthenticated = await secureStorage.isAuthenticated(); // ... redirect logic } catch (e) { debugPrint('Error in redirect: $e'); return '/login'; // Safe fallback } } ``` ## Extension Methods Reference ### Path-Based Navigation ```dart context.goToLogin(); // Go to /login context.goToWarehouses(); // Go to /warehouses context.goToOperations(warehouse); context.goToProducts(warehouse: w, operationType: 'import'); context.goBack(); // Pop current route ``` ### Named Route Navigation ```dart context.goToLoginNamed(); context.goToWarehousesNamed(); context.goToOperationsNamed(warehouse); context.goToProductsNamed(warehouse: w, operationType: 'export'); ``` ## Testing Authentication Flow ### Test Case 1: Fresh Install 1. App starts → No token → Redirects to `/login` 2. User logs in → Token saved → Redirects to `/warehouses` 3. User selects warehouse → Navigates to `/operations` 4. User selects operation → Navigates to `/products` ### Test Case 2: Logged In User 1. App starts → Token exists → Shows `/warehouses` 2. User navigates normally through app 3. User logs out → Token cleared → Redirects to `/login` ### Test Case 3: Manual URL Entry 1. User tries to access `/products` directly 2. Router checks authentication 3. If not authenticated → Redirects to `/login` 4. If authenticated but missing params → Redirects to `/warehouses` ## Troubleshooting ### Problem: Stuck on login page after successful login **Solution**: Check if token is being saved to SecureStorage ```dart // In LoginUseCase await secureStorage.saveAccessToken(user.accessToken); ``` ### Problem: Redirect loop between login and warehouses **Solution**: Verify `isAuthenticated()` logic ```dart // Should return true only if token exists Future isAuthenticated() async { final token = await getAccessToken(); return token != null && token.isNotEmpty; } ``` ### Problem: Navigation parameters are null **Solution**: Use extension methods with correct types ```dart // Correct context.goToOperations(warehouse); // Wrong - may lose type information context.go('/operations', extra: warehouse); ``` ### Problem: Router doesn't react to auth changes **Solution**: Verify GoRouterRefreshStream is listening ```dart GoRouterRefreshStream(this.ref) { ref.listen( authProvider, // Must be the correct provider (_, __) => notifyListeners(), ); } ``` ## Next Steps 1. **Implement Warehouse Provider** - Create warehouse state management - Load warehouses from API - Integrate with warehouse selection page 2. **Implement Products Provider** - Create products state management - Load products based on warehouse and operation - Integrate with products page 3. **Add Loading States** - Show loading indicators during navigation - Handle network errors gracefully 4. **Add Analytics** - Track navigation events - Monitor authentication flow ## Related Documentation - **Router Details**: `/lib/core/router/README.md` - **Auth Setup**: `/lib/features/auth/di/auth_dependency_injection.dart` - **SecureStorage**: `/lib/core/storage/secure_storage.dart` - **Examples**: `/lib/features/warehouse/presentation/pages/warehouse_selection_page_example.dart` ## Summary The complete GoRouter setup provides: - Authentication-based navigation with auto-redirect - Type-safe parameter passing - Reactive updates on auth state changes - Proper error handling and validation - Easy-to-use extension methods - Integration with existing SecureStorage and Riverpod The app flow is: **Login → Warehouses → Operations → Products** All protected routes automatically redirect to login if user is not authenticated.