runable
This commit is contained in:
454
lib/core/providers/README.md
Normal file
454
lib/core/providers/README.md
Normal file
@@ -0,0 +1,454 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user