# Riverpod 3.0 Provider Architecture

This directory contains core-level providers that are used across the application.

## Directory Structure

```
lib/core/providers/
  ├── connectivity_provider.dart      # Network connectivity monitoring
  ├── provider_examples.dart          # Comprehensive Riverpod 3.0 examples
  └── README.md                       # This file
```

## Setup Instructions

### 1. Install Dependencies

Dependencies are already configured in `pubspec.yaml`:

```yaml
dependencies:
  flutter_riverpod: ^3.0.0
  riverpod_annotation: ^3.0.0

dev_dependencies:
  build_runner: ^2.4.11
  riverpod_generator: ^3.0.0
  riverpod_lint: ^3.0.0
  custom_lint: ^0.7.0
```

Run to install:
```bash
flutter pub get
```

### 2. Generate Code

Run code generation whenever you create or modify providers:

```bash
# Watch mode (auto-regenerates on changes)
dart run build_runner watch -d

# One-time build
dart run build_runner build -d

# Clean and rebuild
dart run build_runner build --delete-conflicting-outputs
```

### 3. Wrap App with ProviderScope

In `main.dart`:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}
```

## Riverpod 3.0 Key Concepts

### @riverpod Annotation

The `@riverpod` annotation is the core of code generation. It automatically generates the appropriate provider type based on your function/class signature.

```dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'my_provider.g.dart';

// Simple value
@riverpod
String myValue(MyValueRef ref) => 'Hello';

// Async value
@riverpod
Future<String> myAsync(MyAsyncRef ref) async => 'Hello';

// Stream
@riverpod
Stream<int> myStream(MyStreamRef ref) => Stream.value(1);

// Mutable state
@riverpod
class MyNotifier extends _$MyNotifier {
  @override
  int build() => 0;

  void increment() => state++;
}

// Async mutable state
@riverpod
class MyAsyncNotifier extends _$MyAsyncNotifier {
  @override
  Future<String> build() async => 'Initial';

  Future<void> update(String value) async {
    state = await AsyncValue.guard(() async => value);
  }
}
```

### Provider Types (Auto-Generated)

1. **Simple Provider** - Immutable value
   - Function returning T → Provider<T>

2. **FutureProvider** - Async value
   - Function returning Future<T> → FutureProvider<T>

3. **StreamProvider** - Stream of values
   - Function returning Stream<T> → StreamProvider<T>

4. **NotifierProvider** - Mutable state with methods
   - Class extending Notifier → NotifierProvider

5. **AsyncNotifierProvider** - Async mutable state
   - Class extending AsyncNotifier → AsyncNotifierProvider

6. **StreamNotifierProvider** - Stream mutable state
   - Class extending StreamNotifier → StreamNotifierProvider

### Family (Parameters)

In Riverpod 3.0, family is just function parameters!

```dart
// Old way (Riverpod 2.x)
final userProvider = FutureProvider.family<User, String>((ref, id) async {
  return fetchUser(id);
});

// New way (Riverpod 3.0)
@riverpod
Future<User> user(UserRef ref, String id) async {
  return fetchUser(id);
}

// Multiple parameters (named, optional, defaults)
@riverpod
Future<List<Post>> posts(
  PostsRef ref, {
  required String userId,
  int page = 1,
  int limit = 20,
  String? category,
}) async {
  return fetchPosts(userId, page, limit, category);
}

// Usage
ref.watch(userProvider('user123'));
ref.watch(postsProvider(userId: 'user123', page: 2, category: 'tech'));
```

### AutoDispose vs KeepAlive

```dart
// AutoDispose (default) - cleaned up when not used
@riverpod
String autoExample(AutoExampleRef ref) => 'Auto disposed';

// KeepAlive - stays alive until app closes
@Riverpod(keepAlive: true)
String keepExample(KeepExampleRef ref) => 'Kept alive';
```

### Unified Ref Type

Riverpod 3.0 uses a single `Ref` type (no more FutureProviderRef, StreamProviderRef, etc.):

```dart
@riverpod
Future<String> example(ExampleRef ref) async {
  // All providers use the same ref type
  ref.watch(otherProvider);
  ref.read(anotherProvider);
  ref.listen(thirdProvider, (prev, next) {});
  ref.invalidate(fourthProvider);
}
```

### ref.mounted Check

Always check `ref.mounted` after async operations in Notifiers:

```dart
@riverpod
class Example extends _$Example {
  @override
  String build() => 'Initial';

  Future<void> updateData() async {
    await Future.delayed(Duration(seconds: 2));

    // Check if provider is still mounted
    if (!ref.mounted) return;

    state = 'Updated';
  }
}
```

### Error Handling with AsyncValue.guard()

```dart
@riverpod
class Data extends _$Data {
  @override
  Future<String> build() async => 'Initial';

  Future<void> update(String value) async {
    state = const AsyncValue.loading();

    // AsyncValue.guard catches errors automatically
    state = await AsyncValue.guard(() async {
      await api.update(value);
      return value;
    });
  }
}
```

## Usage in Widgets

### ConsumerWidget

```dart
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(myProvider);
    return Text(value);
  }
}
```

### ConsumerStatefulWidget

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

class _MyWidgetState extends ConsumerState<MyWidget> {
  @override
  void initState() {
    super.initState();
    // ref is available in all lifecycle methods
  }

  @override
  Widget build(BuildContext context) {
    final value = ref.watch(myProvider);
    return Text(value);
  }
}
```

### Consumer (for optimization)

```dart
Column(
  children: [
    const Text('Static'),
    Consumer(
      builder: (context, ref, child) {
        final count = ref.watch(counterProvider);
        return Text('$count');
      },
    ),
  ],
)
```

## Best Practices

### 1. Use .select() for Optimization

```dart
// Bad - rebuilds on any user change
final user = ref.watch(userProvider);

// Good - rebuilds only when name changes
final name = ref.watch(userProvider.select((user) => user.name));

// Good - rebuilds only when async value has data
final userName = ref.watch(
  userProvider.select((async) => async.value?.name),
);
```

### 2. Provider Dependencies

```dart
@riverpod
Future<Dashboard> dashboard(DashboardRef ref) async {
  // Watch other providers
  final user = await ref.watch(userProvider.future);
  final posts = await ref.watch(postsProvider.future);

  return Dashboard(user: user, posts: posts);
}
```

### 3. Invalidation and Refresh

```dart
// In a widget or notifier
ref.invalidate(userProvider);  // Invalidate
ref.refresh(userProvider);     // Invalidate and re-read
```

### 4. Lifecycle Hooks

```dart
@riverpod
String example(ExampleRef ref) {
  ref.onDispose(() {
    // Clean up
  });

  ref.onCancel(() {
    // Last listener removed
  });

  ref.onResume(() {
    // New listener added after cancel
  });

  return 'value';
}
```

### 5. Testing

```dart
test('counter increments', () {
  final container = ProviderContainer();
  addTearDown(container.dispose);

  expect(container.read(counterProvider), 0);
  container.read(counterProvider.notifier).increment();
  expect(container.read(counterProvider), 1);
});

test('async provider', () async {
  final container = ProviderContainer();
  addTearDown(container.dispose);

  final value = await container.read(userProvider.future);
  expect(value.name, 'John');
});
```

## Migration from Riverpod 2.x

### StateNotifierProvider → NotifierProvider

```dart
// Old (2.x)
class Counter extends StateNotifier<int> {
  Counter() : super(0);
  void increment() => state++;
}
final counterProvider = StateNotifierProvider<Counter, int>(Counter.new);

// New (3.0)
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state++;
}
```

### FutureProvider.family → Function with Parameters

```dart
// Old (2.x)
final userProvider = FutureProvider.family<User, String>((ref, id) async {
  return fetchUser(id);
});

// New (3.0)
@riverpod
Future<User> user(UserRef ref, String id) async {
  return fetchUser(id);
}
```

### Ref Types → Single Ref

```dart
// Old (2.x)
final provider = FutureProvider<String>((FutureProviderRef ref) async {
  return 'value';
});

// New (3.0)
@riverpod
Future<String> provider(ProviderRef ref) async {
  return 'value';
}
```

## Common Patterns

See `provider_examples.dart` for comprehensive examples of:
- Simple providers
- Async providers (FutureProvider pattern)
- Stream providers
- Notifier (mutable state)
- AsyncNotifier (async mutable state)
- StreamNotifier
- Family (parameters)
- Provider composition
- Error handling
- Lifecycle hooks
- Optimization with .select()

## Riverpod Lint Rules

The project is configured with `riverpod_lint` for additional checks:
- `provider_dependencies` - Ensure proper dependency usage
- `scoped_providers_should_specify_dependencies` - Scoped provider safety
- `avoid_public_notifier_properties` - Encapsulation
- `avoid_ref_read_inside_build` - Performance
- `avoid_manual_providers_as_generated_provider_dependency` - Use generated providers
- `functional_ref` - Proper ref usage
- `notifier_build` - Proper Notifier implementation

Run linting:
```bash
dart run custom_lint
```

## Resources

- [Riverpod Documentation](https://riverpod.dev)
- [Code Generation Guide](https://riverpod.dev/docs/concepts/about_code_generation)
- [Migration Guide](https://riverpod.dev/docs/migration/from_state_notifier)
- [Provider Examples](./provider_examples.dart)
