# Dependency Injection with Riverpod

This directory contains the centralized dependency injection setup for the entire application using Riverpod.

## Overview

The `providers.dart` file sets up all Riverpod providers following Clean Architecture principles:
- **Data Layer**: Data sources and repositories
- **Domain Layer**: Use cases (business logic)
- **Presentation Layer**: State notifiers and UI state

## Architecture Pattern

```
UI (ConsumerWidget)
    ↓
StateNotifier (Presentation)
    ↓
UseCase (Domain)
    ↓
Repository (Domain Interface)
    ↓
RepositoryImpl (Data)
    ↓
RemoteDataSource (Data)
    ↓
ApiClient (Core)
```

## Provider Categories

### 1. Core Providers (Infrastructure)
- `secureStorageProvider` - Secure storage singleton
- `apiClientProvider` - HTTP client with auth interceptors

### 2. Auth Feature Providers
- `authProvider` - Main auth state (use this in UI)
- `isAuthenticatedProvider` - Quick auth status check
- `currentUserProvider` - Current user data
- `loginUseCaseProvider` - Login business logic
- `logoutUseCaseProvider` - Logout business logic

### 3. Warehouse Feature Providers
- `warehouseProvider` - Main warehouse state
- `warehousesListProvider` - List of warehouses
- `selectedWarehouseProvider` - Currently selected warehouse
- `getWarehousesUseCaseProvider` - Fetch warehouses logic

### 4. Products Feature Providers
- `productsProvider` - Main products state
- `productsListProvider` - List of products
- `operationTypeProvider` - Import/Export type
- `getProductsUseCaseProvider` - Fetch products logic

## Usage Guide

### Basic Setup

1. **Wrap your app with ProviderScope**:
```dart
void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}
```

2. **Use ConsumerWidget or ConsumerStatefulWidget**:
```dart
class MyPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Access providers here
    final isAuthenticated = ref.watch(isAuthenticatedProvider);
    return Scaffold(body: ...);
  }
}
```

### Common Patterns

#### 1. Watch State (UI rebuilds when state changes)
```dart
final authState = ref.watch(authProvider);
final isLoading = ref.watch(isAuthLoadingProvider);
final products = ref.watch(productsListProvider);
```

#### 2. Read State (One-time read, no rebuild)
```dart
final currentUser = ref.read(currentUserProvider);
```

#### 3. Call Methods on StateNotifier
```dart
// Login
ref.read(authProvider.notifier).login(username, password);

// Logout
ref.read(authProvider.notifier).logout();

// Load warehouses
ref.read(warehouseProvider.notifier).loadWarehouses();

// Select warehouse
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);

// Load products
ref.read(productsProvider.notifier).loadProducts(
  warehouseId,
  warehouseName,
  'import',
);
```

#### 4. Listen to State Changes
```dart
ref.listen<AuthState>(authProvider, (previous, next) {
  if (next.isAuthenticated) {
    // Navigate to home
  }
  if (next.error != null) {
    // Show error dialog
  }
});
```

## Feature Examples

### Authentication Flow

```dart
class LoginPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authState = ref.watch(authProvider);

    // Listen for auth changes
    ref.listen<AuthState>(authProvider, (previous, next) {
      if (next.isAuthenticated) {
        Navigator.pushReplacementNamed(context, '/warehouses');
      }
      if (next.error != null) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(next.error!)),
        );
      }
    });

    return Scaffold(
      body: Column(
        children: [
          TextField(
            controller: usernameController,
            decoration: InputDecoration(labelText: 'Username'),
          ),
          TextField(
            controller: passwordController,
            decoration: InputDecoration(labelText: 'Password'),
            obscureText: true,
          ),
          ElevatedButton(
            onPressed: authState.isLoading
                ? null
                : () {
                    ref.read(authProvider.notifier).login(
                      usernameController.text,
                      passwordController.text,
                    );
                  },
            child: authState.isLoading
                ? CircularProgressIndicator()
                : Text('Login'),
          ),
        ],
      ),
    );
  }
}
```

### Warehouse Selection Flow

```dart
class WarehouseSelectionPage extends ConsumerStatefulWidget {
  @override
  ConsumerState<WarehouseSelectionPage> createState() =>
      _WarehouseSelectionPageState();
}

class _WarehouseSelectionPageState
    extends ConsumerState<WarehouseSelectionPage> {
  @override
  void initState() {
    super.initState();
    // Load warehouses when page opens
    Future.microtask(() {
      ref.read(warehouseProvider.notifier).loadWarehouses();
    });
  }

  @override
  Widget build(BuildContext context) {
    final warehouses = ref.watch(warehousesListProvider);
    final isLoading = ref.watch(isWarehouseLoadingProvider);
    final error = ref.watch(warehouseErrorProvider);

    return Scaffold(
      appBar: AppBar(title: Text('Select Warehouse')),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : error != null
              ? Center(child: Text(error))
              : ListView.builder(
                  itemCount: warehouses.length,
                  itemBuilder: (context, index) {
                    final warehouse = warehouses[index];
                    return ListTile(
                      title: Text(warehouse.name),
                      subtitle: Text(warehouse.code),
                      trailing: Text('${warehouse.totalCount} items'),
                      onTap: () {
                        // Select warehouse
                        ref.read(warehouseProvider.notifier)
                            .selectWarehouse(warehouse);

                        // Navigate to operation selection
                        Navigator.pushNamed(
                          context,
                          '/operations',
                          arguments: warehouse,
                        );
                      },
                    );
                  },
                ),
    );
  }
}
```

### Products List Flow

```dart
class ProductsPage extends ConsumerStatefulWidget {
  final int warehouseId;
  final String warehouseName;
  final String operationType; // 'import' or 'export'

  const ProductsPage({
    required this.warehouseId,
    required this.warehouseName,
    required this.operationType,
  });

  @override
  ConsumerState<ProductsPage> createState() => _ProductsPageState();
}

class _ProductsPageState extends ConsumerState<ProductsPage> {
  @override
  void initState() {
    super.initState();
    // Load products when page opens
    Future.microtask(() {
      ref.read(productsProvider.notifier).loadProducts(
        widget.warehouseId,
        widget.warehouseName,
        widget.operationType,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    final products = ref.watch(productsListProvider);
    final isLoading = ref.watch(isProductsLoadingProvider);
    final error = ref.watch(productsErrorProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('${widget.warehouseName} - ${widget.operationType}'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () {
              ref.read(productsProvider.notifier).refreshProducts();
            },
          ),
        ],
      ),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : error != null
              ? Center(child: Text(error))
              : ListView.builder(
                  itemCount: products.length,
                  itemBuilder: (context, index) {
                    final product = products[index];
                    return ProductListItem(product: product);
                  },
                ),
    );
  }
}
```

### Check Auth Status on App Start

```dart
class MyApp extends ConsumerStatefulWidget {
  @override
  ConsumerState<MyApp> createState() => _MyAppState();
}

class _MyAppState extends ConsumerState<MyApp> {
  @override
  void initState() {
    super.initState();
    // Check if user is already authenticated
    Future.microtask(() {
      ref.read(authProvider.notifier).checkAuthStatus();
    });
  }

  @override
  Widget build(BuildContext context) {
    final isAuthenticated = ref.watch(isAuthenticatedProvider);

    return MaterialApp(
      home: isAuthenticated
          ? WarehouseSelectionPage()
          : LoginPage(),
    );
  }
}
```

## Advanced Usage

### Custom Providers

Create custom computed providers for complex logic:

```dart
// Get warehouses filtered by type
final ngWarehousesProvider = Provider<List<WarehouseEntity>>((ref) {
  final warehouses = ref.watch(warehousesListProvider);
  return warehouses.where((w) => w.isNGWareHouse).toList();
});

// Get products count per operation type
final importProductsCountProvider = Provider<int>((ref) {
  final products = ref.watch(productsListProvider);
  final operationType = ref.watch(operationTypeProvider);
  return operationType == 'import' ? products.length : 0;
});
```

### Override Providers (for testing)

```dart
testWidgets('Login page test', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        // Override with mock
        authRepositoryProvider.overrideWithValue(mockAuthRepository),
      ],
      child: LoginPage(),
    ),
  );
});
```

## Best Practices

1. **Use ConsumerWidget**: Always use `ConsumerWidget` or `ConsumerStatefulWidget` to access providers.

2. **Watch in build()**: Only watch providers in the `build()` method for reactive updates.

3. **Read for actions**: Use `ref.read()` for one-time reads or calling methods.

4. **Listen for side effects**: Use `ref.listen()` for navigation, dialogs, snackbars.

5. **Avoid over-watching**: Don't watch entire state if you only need one field - use derived providers.

6. **Keep providers pure**: Don't perform side effects in provider definitions.

7. **Dispose properly**: StateNotifier automatically disposes, but be careful with custom providers.

## Debugging

### Enable Logging
```dart
void main() {
  runApp(
    ProviderScope(
      observers: [ProviderLogger()],
      child: MyApp(),
    ),
  );
}

class ProviderLogger extends ProviderObserver {
  @override
  void didUpdateProvider(
    ProviderBase provider,
    Object? previousValue,
    Object? newValue,
    ProviderContainer container,
  ) {
    print('''
{
  "provider": "${provider.name ?? provider.runtimeType}",
  "newValue": "$newValue"
}''');
  }
}
```

### Common Issues

1. **Provider not found**: Make sure `ProviderScope` wraps your app.

2. **State not updating**: Use `ref.watch()` instead of `ref.read()` in build method.

3. **Circular dependency**: Check provider dependencies - avoid circular references.

4. **Memory leaks**: Use `autoDispose` modifier for providers that should be disposed.

## Testing

### Unit Testing Providers

```dart
test('Auth provider login success', () async {
  final container = ProviderContainer(
    overrides: [
      authRepositoryProvider.overrideWithValue(mockAuthRepository),
    ],
  );

  when(mockAuthRepository.login(any))
      .thenAnswer((_) async => Right(mockUser));

  await container.read(authProvider.notifier).login('user', 'pass');

  expect(container.read(isAuthenticatedProvider), true);
  expect(container.read(currentUserProvider), mockUser);

  container.dispose();
});
```

### Widget Testing

```dart
testWidgets('Login button triggers login', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        authRepositoryProvider.overrideWithValue(mockAuthRepository),
      ],
      child: MaterialApp(home: LoginPage()),
    ),
  );

  await tester.enterText(find.byKey(Key('username')), 'testuser');
  await tester.enterText(find.byKey(Key('password')), 'password');
  await tester.tap(find.byKey(Key('loginButton')));
  await tester.pump();

  verify(mockAuthRepository.login(any)).called(1);
});
```

## Migration Guide

If you were using GetIt or other DI solutions:

1. Replace GetIt registration with Riverpod providers
2. Change `GetIt.instance.get<T>()` to `ref.watch(provider)`
3. Use `ConsumerWidget` instead of regular `StatelessWidget`
4. Move initialization logic to `initState()` or provider initialization

## Resources

- [Riverpod Documentation](https://riverpod.dev)
- [Clean Architecture Guide](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [Flutter State Management](https://docs.flutter.dev/development/data-and-backend/state-mgmt/options)

## Support

For questions or issues with DI setup, contact the development team or refer to the project documentation.
