Files
minhthu/lib/features/auth/INTEGRATION_GUIDE.md
2025-10-28 00:09:46 +07:00

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

  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

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

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