9.5 KiB
9.5 KiB
Authentication Feature Integration Guide
Quick guide to integrate the authentication feature into the warehouse management app.
Prerequisites
Ensure these dependencies are in pubspec.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
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
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
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
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:
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)
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
-
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
-
Error Handling
- Invalid credentials show error message
- Network errors display properly
- Error messages are user-friendly
-
Persistence
- Close and reopen app stays logged in
- Tokens persisted in secure storage
- Auto-redirect to warehouses if authenticated
-
Logout
- Logout clears tokens
- Redirect to login page after logout
- Cannot access protected routes after logout
-
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
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
- Create Warehouse Feature - Follow similar pattern
- Add Token Refresh - Implement auto token refresh
- Add Remember Me - Optional persistent login
- Add Biometric Auth - Face ID / Touch ID
- Add Unit Tests - Test use cases and repositories
- Add Widget Tests - Test UI components
Additional Resources
Support
For issues or questions:
- Check the main README in
lib/features/auth/README.md - Review the CLAUDE.md for project guidelines
- Check existing code examples in the codebase