385 lines
9.5 KiB
Markdown
385 lines
9.5 KiB
Markdown
# Authentication Feature Integration Guide
|
|
|
|
Quick guide to integrate the authentication feature into the warehouse management app.
|
|
|
|
## Prerequisites
|
|
|
|
Ensure these dependencies are in `pubspec.yaml`:
|
|
```yaml
|
|
dependencies:
|
|
flutter_riverpod: ^2.4.9
|
|
dartz: ^0.10.1
|
|
flutter_secure_storage: ^9.0.0
|
|
dio: ^5.3.2
|
|
equatable: ^2.0.5
|
|
go_router: ^12.1.3
|
|
```
|
|
|
|
## Step 1: Update Main App
|
|
|
|
### Update `lib/main.dart`
|
|
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'core/routing/app_router.dart';
|
|
import 'core/theme/app_theme.dart';
|
|
|
|
void main() {
|
|
runApp(
|
|
const ProviderScope(
|
|
child: MyApp(),
|
|
),
|
|
);
|
|
}
|
|
|
|
class MyApp extends StatelessWidget {
|
|
const MyApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp.router(
|
|
title: 'Warehouse Manager',
|
|
theme: AppTheme.lightTheme,
|
|
darkTheme: AppTheme.darkTheme,
|
|
routerConfig: appRouter,
|
|
debugShowCheckedModeBanner: false,
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 2: Update Router Configuration
|
|
|
|
### Update `lib/core/routing/app_router.dart`
|
|
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../features/auth/auth.dart';
|
|
import '../../features/auth/di/auth_dependency_injection.dart';
|
|
|
|
// Create a global key for navigator
|
|
final navigatorKey = GlobalKey<NavigatorState>();
|
|
|
|
// Router provider
|
|
final appRouterProvider = Provider<GoRouter>((ref) {
|
|
return GoRouter(
|
|
navigatorKey: navigatorKey,
|
|
initialLocation: '/login',
|
|
routes: [
|
|
// Login route
|
|
GoRoute(
|
|
path: '/login',
|
|
name: 'login',
|
|
builder: (context, state) => const LoginPage(),
|
|
),
|
|
|
|
// Warehouses route (protected)
|
|
GoRoute(
|
|
path: '/warehouses',
|
|
name: 'warehouses',
|
|
builder: (context, state) => const WarehouseSelectionPage(), // TODO: Create this
|
|
),
|
|
|
|
// Add more routes as needed...
|
|
],
|
|
|
|
// Redirect logic for authentication
|
|
redirect: (context, state) {
|
|
// Get auth state from provider container
|
|
final container = ProviderScope.containerOf(context);
|
|
final authState = container.read(authProvider);
|
|
|
|
final isAuthenticated = authState.isAuthenticated;
|
|
final isLoggingIn = state.matchedLocation == '/login';
|
|
|
|
// If not authenticated and not on login page, redirect to login
|
|
if (!isAuthenticated && !isLoggingIn) {
|
|
return '/login';
|
|
}
|
|
|
|
// If authenticated and on login page, redirect to warehouses
|
|
if (isAuthenticated && isLoggingIn) {
|
|
return '/warehouses';
|
|
}
|
|
|
|
// No redirect needed
|
|
return null;
|
|
},
|
|
);
|
|
});
|
|
|
|
// Export the router instance
|
|
final appRouter = GoRouter(
|
|
navigatorKey: navigatorKey,
|
|
initialLocation: '/login',
|
|
routes: [
|
|
GoRoute(
|
|
path: '/login',
|
|
name: 'login',
|
|
builder: (context, state) => const LoginPage(),
|
|
),
|
|
GoRoute(
|
|
path: '/warehouses',
|
|
name: 'warehouses',
|
|
builder: (context, state) => const Scaffold(
|
|
body: Center(child: Text('Warehouses Page - TODO')),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
```
|
|
|
|
## Step 3: Configure API Base URL
|
|
|
|
### Update `lib/core/constants/app_constants.dart`
|
|
|
|
```dart
|
|
class AppConstants {
|
|
// API Configuration
|
|
static const String apiBaseUrl = 'https://your-api-url.com';
|
|
static const int connectionTimeout = 30000;
|
|
static const int receiveTimeout = 30000;
|
|
static const int sendTimeout = 30000;
|
|
|
|
// Other constants...
|
|
}
|
|
```
|
|
|
|
## Step 4: Create Protected Route Wrapper (Optional)
|
|
|
|
### Create `lib/core/widgets/protected_route.dart`
|
|
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../features/auth/di/auth_dependency_injection.dart';
|
|
|
|
class ProtectedRoute extends ConsumerWidget {
|
|
final Widget child;
|
|
|
|
const ProtectedRoute({
|
|
super.key,
|
|
required this.child,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final authState = ref.watch(authProvider);
|
|
|
|
// Show loading while checking auth
|
|
if (authState.isLoading) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Redirect to login if not authenticated
|
|
if (!authState.isAuthenticated) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
context.go('/login');
|
|
});
|
|
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
// Show protected content
|
|
return child;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 5: Add Logout Button (Optional)
|
|
|
|
### Example usage in any page:
|
|
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import 'package:minhthu/features/auth/di/auth_dependency_injection.dart';
|
|
|
|
class SettingsPage extends ConsumerWidget {
|
|
const SettingsPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Settings'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.logout),
|
|
onPressed: () {
|
|
// Show confirmation dialog
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Logout'),
|
|
content: const Text('Are you sure you want to logout?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Cancel'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
ref.read(authProvider.notifier).logout();
|
|
},
|
|
child: const Text('Logout'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: const Center(
|
|
child: Text('Settings'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 6: Handle API Client Setup
|
|
|
|
### Update `lib/core/di/core_providers.dart` (create if needed)
|
|
|
|
```dart
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../network/api_client.dart';
|
|
import '../storage/secure_storage.dart';
|
|
|
|
/// Provider for SecureStorage singleton
|
|
final secureStorageProvider = Provider<SecureStorage>((ref) {
|
|
return SecureStorage();
|
|
});
|
|
|
|
/// Provider for ApiClient
|
|
final apiClientProvider = Provider<ApiClient>((ref) {
|
|
final secureStorage = ref.watch(secureStorageProvider);
|
|
|
|
final apiClient = ApiClient(secureStorage);
|
|
|
|
// Set up unauthorized callback to handle 401 errors
|
|
apiClient.onUnauthorized = () {
|
|
// Navigate to login when unauthorized
|
|
// This can be enhanced with proper navigation context
|
|
};
|
|
|
|
return apiClient;
|
|
});
|
|
```
|
|
|
|
## Step 7: Test the Integration
|
|
|
|
### Manual Testing Checklist
|
|
|
|
1. **Login Flow**
|
|
- [ ] App starts on login page
|
|
- [ ] Form validation works
|
|
- [ ] Login with valid credentials succeeds
|
|
- [ ] Navigate to warehouses page after login
|
|
- [ ] Tokens saved in secure storage
|
|
|
|
2. **Error Handling**
|
|
- [ ] Invalid credentials show error message
|
|
- [ ] Network errors display properly
|
|
- [ ] Error messages are user-friendly
|
|
|
|
3. **Persistence**
|
|
- [ ] Close and reopen app stays logged in
|
|
- [ ] Tokens persisted in secure storage
|
|
- [ ] Auto-redirect to warehouses if authenticated
|
|
|
|
4. **Logout**
|
|
- [ ] Logout clears tokens
|
|
- [ ] Redirect to login page after logout
|
|
- [ ] Cannot access protected routes after logout
|
|
|
|
5. **Loading States**
|
|
- [ ] Loading indicator shows during login
|
|
- [ ] Form disabled during loading
|
|
- [ ] No double submissions
|
|
|
|
## Step 8: Environment Configuration (Optional)
|
|
|
|
### Create `lib/core/config/environment.dart`
|
|
|
|
```dart
|
|
enum Environment {
|
|
development,
|
|
staging,
|
|
production,
|
|
}
|
|
|
|
class EnvironmentConfig {
|
|
static Environment current = Environment.development;
|
|
|
|
static String get apiBaseUrl {
|
|
switch (current) {
|
|
case Environment.development:
|
|
return 'https://dev-api.example.com';
|
|
case Environment.staging:
|
|
return 'https://staging-api.example.com';
|
|
case Environment.production:
|
|
return 'https://api.example.com';
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: "Provider not found"
|
|
**Solution**: Ensure `ProviderScope` wraps your app in `main.dart`
|
|
|
|
### Issue: "Navigation doesn't work"
|
|
**Solution**: Verify router configuration and route names match
|
|
|
|
### Issue: "Secure storage error"
|
|
**Solution**:
|
|
- Add platform-specific configurations
|
|
- Check app permissions
|
|
- Clear app data and reinstall
|
|
|
|
### Issue: "API calls fail"
|
|
**Solution**:
|
|
- Verify API base URL in `app_constants.dart`
|
|
- Check network connectivity
|
|
- Verify API endpoint paths in `api_endpoints.dart`
|
|
|
|
## Next Steps
|
|
|
|
1. **Create Warehouse Feature** - Follow similar pattern
|
|
2. **Add Token Refresh** - Implement auto token refresh
|
|
3. **Add Remember Me** - Optional persistent login
|
|
4. **Add Biometric Auth** - Face ID / Touch ID
|
|
5. **Add Unit Tests** - Test use cases and repositories
|
|
6. **Add Widget Tests** - Test UI components
|
|
|
|
## Additional Resources
|
|
|
|
- [Riverpod Documentation](https://riverpod.dev/)
|
|
- [Go Router Documentation](https://pub.dev/packages/go_router)
|
|
- [Clean Architecture Guide](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
|
- [Flutter Secure Storage](https://pub.dev/packages/flutter_secure_storage)
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check the main README in `lib/features/auth/README.md`
|
|
2. Review the CLAUDE.md for project guidelines
|
|
3. Check existing code examples in the codebase
|