runable
This commit is contained in:
462
lib/core/providers/QUICK_REFERENCE.md
Normal file
462
lib/core/providers/QUICK_REFERENCE.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# Riverpod 3.0 Quick Reference Card
|
||||
|
||||
## File Structure
|
||||
```dart
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'my_provider.g.dart'; // REQUIRED!
|
||||
|
||||
// Your providers here
|
||||
```
|
||||
|
||||
## Provider Types
|
||||
|
||||
### 1. Simple Value (Immutable)
|
||||
```dart
|
||||
@riverpod
|
||||
String appName(AppNameRef ref) => 'Worker App';
|
||||
|
||||
// Usage
|
||||
final name = ref.watch(appNameProvider);
|
||||
```
|
||||
|
||||
### 2. Async Value (Future)
|
||||
```dart
|
||||
@riverpod
|
||||
Future<User> user(UserRef ref, String id) async {
|
||||
return await fetchUser(id);
|
||||
}
|
||||
|
||||
// Usage
|
||||
final userAsync = ref.watch(userProvider('123'));
|
||||
userAsync.when(
|
||||
data: (user) => Text(user.name),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
error: (e, _) => Text('Error: $e'),
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Stream
|
||||
```dart
|
||||
@riverpod
|
||||
Stream<Message> messages(MessagesRef ref) {
|
||||
return webSocket.messages;
|
||||
}
|
||||
|
||||
// Usage
|
||||
final messages = ref.watch(messagesProvider);
|
||||
```
|
||||
|
||||
### 4. Mutable State (Notifier)
|
||||
```dart
|
||||
@riverpod
|
||||
class Counter extends _$Counter {
|
||||
@override
|
||||
int build() => 0;
|
||||
|
||||
void increment() => state++;
|
||||
void decrement() => state--;
|
||||
}
|
||||
|
||||
// Usage
|
||||
final count = ref.watch(counterProvider);
|
||||
ref.read(counterProvider.notifier).increment();
|
||||
```
|
||||
|
||||
### 5. Async Mutable State (AsyncNotifier)
|
||||
```dart
|
||||
@riverpod
|
||||
class Profile extends _$Profile {
|
||||
@override
|
||||
Future<User> build() async {
|
||||
return await api.getProfile();
|
||||
}
|
||||
|
||||
Future<void> update(String name) async {
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
return await api.updateProfile(name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
final profile = ref.watch(profileProvider);
|
||||
await ref.read(profileProvider.notifier).update('New Name');
|
||||
```
|
||||
|
||||
## Family (Parameters)
|
||||
|
||||
```dart
|
||||
// Single parameter
|
||||
@riverpod
|
||||
Future<Post> post(PostRef ref, String id) async {
|
||||
return await api.getPost(id);
|
||||
}
|
||||
|
||||
// Multiple parameters
|
||||
@riverpod
|
||||
Future<List<Post>> posts(
|
||||
PostsRef ref, {
|
||||
required String userId,
|
||||
int page = 1,
|
||||
String? category,
|
||||
}) async {
|
||||
return await api.getPosts(userId, page, category);
|
||||
}
|
||||
|
||||
// Usage
|
||||
ref.watch(postProvider('post-123'));
|
||||
ref.watch(postsProvider(userId: 'user-1', page: 2));
|
||||
```
|
||||
|
||||
## AutoDispose vs KeepAlive
|
||||
|
||||
```dart
|
||||
// AutoDispose (default) - cleaned up when not used
|
||||
@riverpod
|
||||
String temp(TempRef ref) => 'Auto disposed';
|
||||
|
||||
// KeepAlive - stays alive
|
||||
@Riverpod(keepAlive: true)
|
||||
String config(ConfigRef ref) => 'Global config';
|
||||
```
|
||||
|
||||
## 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
|
||||
Widget build(BuildContext context) {
|
||||
final value = ref.watch(myProvider);
|
||||
return Text(value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Consumer (optimization)
|
||||
```dart
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final count = ref.watch(counterProvider);
|
||||
return Text('$count');
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Ref Methods
|
||||
|
||||
### ref.watch() - Use in build
|
||||
```dart
|
||||
// Rebuilds when value changes
|
||||
final value = ref.watch(myProvider);
|
||||
```
|
||||
|
||||
### ref.read() - Use in callbacks
|
||||
```dart
|
||||
// One-time read, doesn't listen
|
||||
onPressed: () {
|
||||
ref.read(myProvider.notifier).update();
|
||||
}
|
||||
```
|
||||
|
||||
### ref.listen() - Side effects
|
||||
```dart
|
||||
ref.listen(authProvider, (prev, next) {
|
||||
if (next.isLoggedOut) {
|
||||
Navigator.of(context).pushReplacementNamed('/login');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### ref.invalidate() - Force refresh
|
||||
```dart
|
||||
ref.invalidate(userProvider);
|
||||
```
|
||||
|
||||
### ref.refresh() - Invalidate and read
|
||||
```dart
|
||||
final newValue = ref.refresh(userProvider);
|
||||
```
|
||||
|
||||
## AsyncValue Handling
|
||||
|
||||
### .when()
|
||||
```dart
|
||||
asyncValue.when(
|
||||
data: (value) => Text(value),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
```
|
||||
|
||||
### Pattern Matching (Dart 3+)
|
||||
```dart
|
||||
switch (asyncValue) {
|
||||
case AsyncData(:final value):
|
||||
return Text(value);
|
||||
case AsyncError(:final error):
|
||||
return Text('Error: $error');
|
||||
case AsyncLoading():
|
||||
return CircularProgressIndicator();
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Checks
|
||||
```dart
|
||||
if (asyncValue.isLoading) return Loading();
|
||||
if (asyncValue.hasError) return Error();
|
||||
final data = asyncValue.value!;
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Use .select()
|
||||
```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),
|
||||
);
|
||||
|
||||
// With AsyncValue
|
||||
final name = ref.watch(
|
||||
userProvider.select((async) => async.value?.name),
|
||||
);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### AsyncValue.guard()
|
||||
```dart
|
||||
@riverpod
|
||||
class Data extends _$Data {
|
||||
@override
|
||||
Future<String> build() async => 'Initial';
|
||||
|
||||
Future<void> update(String value) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// Catches errors automatically
|
||||
state = await AsyncValue.guard(() async {
|
||||
return await api.update(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Provider Composition
|
||||
|
||||
```dart
|
||||
@riverpod
|
||||
Future<Dashboard> dashboard(DashboardRef ref) async {
|
||||
// Depend on other providers
|
||||
final user = await ref.watch(userProvider.future);
|
||||
final posts = await ref.watch(postsProvider.future);
|
||||
|
||||
return Dashboard(user: user, posts: posts);
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Hooks
|
||||
|
||||
```dart
|
||||
@riverpod
|
||||
String example(ExampleRef ref) {
|
||||
ref.onDispose(() {
|
||||
// Cleanup
|
||||
print('Disposed');
|
||||
});
|
||||
|
||||
ref.onCancel(() {
|
||||
// Last listener removed
|
||||
});
|
||||
|
||||
ref.onResume(() {
|
||||
// New listener added
|
||||
});
|
||||
|
||||
return 'value';
|
||||
}
|
||||
```
|
||||
|
||||
## ref.mounted Check (Riverpod 3.0)
|
||||
|
||||
```dart
|
||||
@riverpod
|
||||
class Example extends _$Example {
|
||||
@override
|
||||
String build() => 'Initial';
|
||||
|
||||
Future<void> update() async {
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
|
||||
// Check if still mounted
|
||||
if (!ref.mounted) return;
|
||||
|
||||
state = 'Updated';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Code Generation Commands
|
||||
|
||||
```bash
|
||||
# Watch mode (recommended)
|
||||
dart run build_runner watch -d
|
||||
|
||||
# One-time build
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# Clean and rebuild
|
||||
dart run build_runner clean && dart run build_runner build -d
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
```bash
|
||||
# Check Riverpod issues
|
||||
dart run custom_lint
|
||||
|
||||
# Auto-fix
|
||||
dart run custom_lint --fix
|
||||
```
|
||||
|
||||
## 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);
|
||||
});
|
||||
```
|
||||
|
||||
## Widget Testing
|
||||
|
||||
```dart
|
||||
testWidgets('test', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
userProvider.overrideWith((ref) => User(name: 'Test')),
|
||||
],
|
||||
child: MaterialApp(home: MyScreen()),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Test'), findsOneWidget);
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
✅ **DO:**
|
||||
- Use `ref.watch()` in build methods
|
||||
- Use `ref.read()` in event handlers
|
||||
- Use `.select()` to optimize rebuilds
|
||||
- Check `ref.mounted` after async operations
|
||||
- Use `AsyncValue.guard()` for error handling
|
||||
- Use autoDispose for temporary state
|
||||
- Keep providers in dedicated directories
|
||||
|
||||
❌ **DON'T:**
|
||||
- Use `ref.read()` in build methods
|
||||
- Forget the `part` directive
|
||||
- Use deprecated `StateNotifierProvider`
|
||||
- Create providers without code generation
|
||||
- Forget to run build_runner after changes
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Loading State
|
||||
```dart
|
||||
Future<void> save() async {
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() => api.save());
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination
|
||||
```dart
|
||||
@riverpod
|
||||
class PostList extends _$PostList {
|
||||
@override
|
||||
Future<List<Post>> build() => _fetch(0);
|
||||
|
||||
int _page = 0;
|
||||
|
||||
Future<void> loadMore() async {
|
||||
final current = state.value ?? [];
|
||||
_page++;
|
||||
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
final newPosts = await _fetch(_page);
|
||||
return [...current, ...newPosts];
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<Post>> _fetch(int page) async {
|
||||
return await api.getPosts(page: page);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Form State
|
||||
```dart
|
||||
@riverpod
|
||||
class LoginForm extends _$LoginForm {
|
||||
@override
|
||||
LoginFormState build() => LoginFormState();
|
||||
|
||||
void setEmail(String email) {
|
||||
state = state.copyWith(email: email);
|
||||
}
|
||||
|
||||
void setPassword(String password) {
|
||||
state = state.copyWith(password: password);
|
||||
}
|
||||
|
||||
Future<void> submit() async {
|
||||
if (!state.isValid) return;
|
||||
|
||||
state = state.copyWith(isLoading: true);
|
||||
try {
|
||||
await api.login(state.email, state.password);
|
||||
state = state.copyWith(isLoading: false, success: true);
|
||||
} catch (e) {
|
||||
state = state.copyWith(isLoading: false, error: e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- 📄 provider_examples.dart - All patterns with examples
|
||||
- 📄 connectivity_provider.dart - Real-world implementation
|
||||
- 📄 RIVERPOD_SETUP.md - Complete guide
|
||||
- 🌐 https://riverpod.dev - Official documentation
|
||||
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)
|
||||
113
lib/core/providers/connectivity_provider.dart
Normal file
113
lib/core/providers/connectivity_provider.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'connectivity_provider.g.dart';
|
||||
|
||||
/// Enum representing connectivity status
|
||||
enum ConnectivityStatus {
|
||||
/// Connected to WiFi
|
||||
wifi,
|
||||
|
||||
/// Connected to mobile data
|
||||
mobile,
|
||||
|
||||
/// No internet connection
|
||||
offline,
|
||||
}
|
||||
|
||||
/// Provider for the Connectivity instance
|
||||
/// This is a simple provider that returns a singleton instance
|
||||
@riverpod
|
||||
Connectivity connectivity(Ref ref) {
|
||||
return Connectivity();
|
||||
}
|
||||
|
||||
/// Stream provider that monitors real-time connectivity changes
|
||||
/// This automatically updates whenever the device connectivity changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@riverpod
|
||||
Stream<ConnectivityStatus> connectivityStream(Ref ref) {
|
||||
final connectivity = ref.watch(connectivityProvider);
|
||||
|
||||
return connectivity.onConnectivityChanged.map((result) {
|
||||
// Handle the List<ConnectivityResult> from connectivity_plus
|
||||
if (result.contains(ConnectivityResult.wifi)) {
|
||||
return ConnectivityStatus.wifi;
|
||||
} else if (result.contains(ConnectivityResult.mobile)) {
|
||||
return ConnectivityStatus.mobile;
|
||||
} else {
|
||||
return ConnectivityStatus.offline;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Provider that checks current connectivity status once
|
||||
/// This is useful for one-time checks without listening to changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final status = await ref.read(currentConnectivityProvider.future);
|
||||
/// if (status == ConnectivityStatus.offline) {
|
||||
/// showOfflineDialog();
|
||||
/// }
|
||||
/// ```
|
||||
@riverpod
|
||||
Future<ConnectivityStatus> currentConnectivity(Ref ref) async {
|
||||
final connectivity = ref.watch(connectivityProvider);
|
||||
final result = await connectivity.checkConnectivity();
|
||||
|
||||
// Handle the List<ConnectivityResult>
|
||||
if (result.contains(ConnectivityResult.wifi)) {
|
||||
return ConnectivityStatus.wifi;
|
||||
} else if (result.contains(ConnectivityResult.mobile)) {
|
||||
return ConnectivityStatus.mobile;
|
||||
} else {
|
||||
return ConnectivityStatus.offline;
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider that returns whether the device is currently online
|
||||
/// Convenient boolean check for connectivity
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
@riverpod
|
||||
Stream<bool> isOnline(Ref ref) {
|
||||
// Get the connectivity stream and map it to a boolean
|
||||
final connectivity = ref.watch(connectivityProvider);
|
||||
|
||||
return connectivity.onConnectivityChanged.map((result) {
|
||||
// Online if connected to WiFi or mobile
|
||||
return result.contains(ConnectivityResult.wifi) ||
|
||||
result.contains(ConnectivityResult.mobile);
|
||||
});
|
||||
}
|
||||
|
||||
/// Example of using .select() for optimization
|
||||
/// Only rebuilds when the online status changes, not on WiFi<->Mobile switches
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// // This only rebuilds when going online/offline
|
||||
/// final isOnline = ref.watch(
|
||||
/// connectivityStreamProvider.select((async) =>
|
||||
/// async.value != ConnectivityStatus.offline
|
||||
/// ),
|
||||
/// );
|
||||
/// ```
|
||||
283
lib/core/providers/connectivity_provider.g.dart
Normal file
283
lib/core/providers/connectivity_provider.g.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'connectivity_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider for the Connectivity instance
|
||||
/// This is a simple provider that returns a singleton instance
|
||||
|
||||
@ProviderFor(connectivity)
|
||||
const connectivityProvider = ConnectivityProvider._();
|
||||
|
||||
/// Provider for the Connectivity instance
|
||||
/// This is a simple provider that returns a singleton instance
|
||||
|
||||
final class ConnectivityProvider
|
||||
extends $FunctionalProvider<Connectivity, Connectivity, Connectivity>
|
||||
with $Provider<Connectivity> {
|
||||
/// Provider for the Connectivity instance
|
||||
/// This is a simple provider that returns a singleton instance
|
||||
const ConnectivityProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'connectivityProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$connectivityHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<Connectivity> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Connectivity create(Ref ref) {
|
||||
return connectivity(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Connectivity value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Connectivity>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$connectivityHash() => r'6d67af0ea4110f6ee0246dd332f90f8901380eda';
|
||||
|
||||
/// Stream provider that monitors real-time connectivity changes
|
||||
/// This automatically updates whenever the device connectivity changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@ProviderFor(connectivityStream)
|
||||
const connectivityStreamProvider = ConnectivityStreamProvider._();
|
||||
|
||||
/// Stream provider that monitors real-time connectivity changes
|
||||
/// This automatically updates whenever the device connectivity changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
final class ConnectivityStreamProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<ConnectivityStatus>,
|
||||
ConnectivityStatus,
|
||||
Stream<ConnectivityStatus>
|
||||
>
|
||||
with
|
||||
$FutureModifier<ConnectivityStatus>,
|
||||
$StreamProvider<ConnectivityStatus> {
|
||||
/// Stream provider that monitors real-time connectivity changes
|
||||
/// This automatically updates whenever the device connectivity changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final connectivityState = ref.watch(connectivityStreamProvider);
|
||||
/// connectivityState.when(
|
||||
/// data: (status) => Text('Status: $status'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
const ConnectivityStreamProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'connectivityStreamProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$connectivityStreamHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<ConnectivityStatus> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<ConnectivityStatus> create(Ref ref) {
|
||||
return connectivityStream(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$connectivityStreamHash() =>
|
||||
r'207d7c426c0182225f4d1fd2014b9bc6c667fd67';
|
||||
|
||||
/// Provider that checks current connectivity status once
|
||||
/// This is useful for one-time checks without listening to changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final status = await ref.read(currentConnectivityProvider.future);
|
||||
/// if (status == ConnectivityStatus.offline) {
|
||||
/// showOfflineDialog();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@ProviderFor(currentConnectivity)
|
||||
const currentConnectivityProvider = CurrentConnectivityProvider._();
|
||||
|
||||
/// Provider that checks current connectivity status once
|
||||
/// This is useful for one-time checks without listening to changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final status = await ref.read(currentConnectivityProvider.future);
|
||||
/// if (status == ConnectivityStatus.offline) {
|
||||
/// showOfflineDialog();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
final class CurrentConnectivityProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<ConnectivityStatus>,
|
||||
ConnectivityStatus,
|
||||
FutureOr<ConnectivityStatus>
|
||||
>
|
||||
with
|
||||
$FutureModifier<ConnectivityStatus>,
|
||||
$FutureProvider<ConnectivityStatus> {
|
||||
/// Provider that checks current connectivity status once
|
||||
/// This is useful for one-time checks without listening to changes
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final status = await ref.read(currentConnectivityProvider.future);
|
||||
/// if (status == ConnectivityStatus.offline) {
|
||||
/// showOfflineDialog();
|
||||
/// }
|
||||
/// ```
|
||||
const CurrentConnectivityProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'currentConnectivityProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$currentConnectivityHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<ConnectivityStatus> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<ConnectivityStatus> create(Ref ref) {
|
||||
return currentConnectivity(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$currentConnectivityHash() =>
|
||||
r'bf11d5eef553f9476a8b667e68572268bc25c9fb';
|
||||
|
||||
/// Provider that returns whether the device is currently online
|
||||
/// Convenient boolean check for connectivity
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
@ProviderFor(isOnline)
|
||||
const isOnlineProvider = IsOnlineProvider._();
|
||||
|
||||
/// Provider that returns whether the device is currently online
|
||||
/// Convenient boolean check for connectivity
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
final class IsOnlineProvider
|
||||
extends $FunctionalProvider<AsyncValue<bool>, bool, Stream<bool>>
|
||||
with $FutureModifier<bool>, $StreamProvider<bool> {
|
||||
/// Provider that returns whether the device is currently online
|
||||
/// Convenient boolean check for connectivity
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final isOnlineAsync = ref.watch(isOnlineProvider);
|
||||
/// isOnlineAsync.when(
|
||||
/// data: (isOnline) => isOnline ? Text('Online') : Text('Offline'),
|
||||
/// loading: () => CircularProgressIndicator(),
|
||||
/// error: (error, _) => Text('Error: $error'),
|
||||
/// );
|
||||
/// ```
|
||||
const IsOnlineProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'isOnlineProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$isOnlineHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<bool> $createElement($ProviderPointer pointer) =>
|
||||
$StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<bool> create(Ref ref) {
|
||||
return isOnline(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$isOnlineHash() => r'09f68fd322b995ffdc28fab6249d8b80108512c4';
|
||||
474
lib/core/providers/provider_examples.dart
Normal file
474
lib/core/providers/provider_examples.dart
Normal file
@@ -0,0 +1,474 @@
|
||||
// ignore_for_file: unreachable_from_main
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'provider_examples.g.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// RIVERPOD 3.0 PROVIDER EXAMPLES WITH CODE GENERATION
|
||||
/// ============================================================================
|
||||
/// This file contains comprehensive examples of Riverpod 3.0 patterns
|
||||
/// using the @riverpod annotation and code generation.
|
||||
///
|
||||
/// Key Changes in Riverpod 3.0:
|
||||
/// - Unified Ref type (no more FutureProviderRef, StreamProviderRef, etc.)
|
||||
/// - Simplified Notifier classes (no more separate AutoDisposeNotifier)
|
||||
/// - Automatic retry for failed providers
|
||||
/// - ref.mounted check after async operations
|
||||
/// - Improved family parameters (just function parameters!)
|
||||
/// ============================================================================
|
||||
|
||||
// ============================================================================
|
||||
// 1. SIMPLE IMMUTABLE VALUE PROVIDER
|
||||
// ============================================================================
|
||||
|
||||
/// Simple provider that returns an immutable value
|
||||
/// Use this for constants, configurations, or computed values
|
||||
@riverpod
|
||||
String appVersion(Ref ref) {
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
/// Provider with computation
|
||||
@riverpod
|
||||
int pointsMultiplier(Ref ref) {
|
||||
// Can read other providers
|
||||
final userTier = 'diamond'; // This would come from another provider
|
||||
return userTier == 'diamond' ? 3 : userTier == 'platinum' ? 2 : 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2. ASYNC DATA FETCHING (FutureProvider pattern)
|
||||
// ============================================================================
|
||||
|
||||
/// Async provider for fetching data once
|
||||
/// Automatically handles loading and error states via AsyncValue
|
||||
@riverpod
|
||||
Future<String> userData(Ref ref) async {
|
||||
// Simulate API call
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return 'User Data';
|
||||
}
|
||||
|
||||
/// Async provider with parameters (Family pattern)
|
||||
/// Parameters are just function parameters - much simpler than before!
|
||||
@riverpod
|
||||
Future<String> userProfile(Ref ref, String userId) async {
|
||||
// Simulate API call with userId
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return 'Profile for user: $userId';
|
||||
}
|
||||
|
||||
/// Async provider with multiple parameters
|
||||
/// Named parameters, optional parameters, defaults - all supported!
|
||||
@riverpod
|
||||
Future<List<String>> productList(
|
||||
Ref ref, {
|
||||
required String category,
|
||||
int page = 1,
|
||||
int limit = 20,
|
||||
String? searchQuery,
|
||||
}) async {
|
||||
// Simulate API call with parameters
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return ['Product 1', 'Product 2', 'Product 3'];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 3. STREAM PROVIDER
|
||||
// ============================================================================
|
||||
|
||||
/// Stream provider for real-time data
|
||||
/// Use this for WebSocket connections, real-time updates, etc.
|
||||
@riverpod
|
||||
Stream<int> timer(Ref ref) {
|
||||
return Stream.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(count) => count,
|
||||
);
|
||||
}
|
||||
|
||||
/// Stream provider with parameters
|
||||
@riverpod
|
||||
Stream<String> chatMessages(Ref ref, String roomId) {
|
||||
// Simulate WebSocket stream
|
||||
return Stream.periodic(
|
||||
const Duration(seconds: 2),
|
||||
(count) => 'Message $count in room $roomId',
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4. NOTIFIER - MUTABLE STATE WITH METHODS
|
||||
// ============================================================================
|
||||
|
||||
/// Notifier for mutable state with methods
|
||||
/// Use this when you need to expose methods to modify state
|
||||
///
|
||||
/// The @riverpod annotation generates:
|
||||
/// - counterProvider: Access the state
|
||||
/// - counterProvider.notifier: Access the notifier methods
|
||||
@riverpod
|
||||
class Counter extends _$Counter {
|
||||
/// Build method returns the initial state
|
||||
@override
|
||||
int build() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Methods to modify state
|
||||
void increment() {
|
||||
state++;
|
||||
}
|
||||
|
||||
void decrement() {
|
||||
state--;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
state = 0;
|
||||
}
|
||||
|
||||
void add(int value) {
|
||||
state += value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Notifier with parameters (Family pattern)
|
||||
@riverpod
|
||||
class CartQuantity extends _$CartQuantity {
|
||||
/// Parameters become properties you can access
|
||||
@override
|
||||
int build(String productId) {
|
||||
// Initialize with 0 or load from local storage
|
||||
return 0;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
state++;
|
||||
}
|
||||
|
||||
void decrement() {
|
||||
if (state > 0) {
|
||||
state--;
|
||||
}
|
||||
}
|
||||
|
||||
void setQuantity(int quantity) {
|
||||
state = quantity.clamp(0, 99);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 5. ASYNC NOTIFIER - MUTABLE STATE WITH ASYNC INITIALIZATION
|
||||
// ============================================================================
|
||||
|
||||
/// AsyncNotifier for state that requires async initialization
|
||||
/// Perfect for fetching data that can then be modified
|
||||
///
|
||||
/// State type: AsyncValue<UserProfile>
|
||||
/// - AsyncValue.data(profile) when loaded
|
||||
/// - AsyncValue.loading() when loading
|
||||
/// - AsyncValue.error(error, stack) when error
|
||||
@riverpod
|
||||
class UserProfileNotifier extends _$UserProfileNotifier {
|
||||
/// Build method returns Future of the initial state
|
||||
@override
|
||||
Future<UserProfileData> build() async {
|
||||
// Fetch initial data
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return UserProfileData(name: 'John Doe', email: 'john@example.com');
|
||||
}
|
||||
|
||||
/// Method to update profile
|
||||
/// Uses AsyncValue.guard() for proper error handling
|
||||
Future<void> updateName(String name) async {
|
||||
// Set loading state
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// Update state with error handling
|
||||
state = await AsyncValue.guard(() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
final currentProfile = await future; // Get current data
|
||||
return currentProfile.copyWith(name: name);
|
||||
});
|
||||
}
|
||||
|
||||
/// Refresh data
|
||||
Future<void> refresh() async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
state = await AsyncValue.guard(() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return UserProfileData(name: 'Refreshed', email: 'refresh@example.com');
|
||||
});
|
||||
}
|
||||
|
||||
/// Method with ref.mounted check (Riverpod 3.0 feature)
|
||||
Future<void> updateWithMountedCheck(String name) async {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
// Check if provider is still mounted after async operation
|
||||
if (!ref.mounted) return;
|
||||
|
||||
state = AsyncValue.data(
|
||||
UserProfileData(name: name, email: 'email@example.com'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Simple data class for example
|
||||
class UserProfileData {
|
||||
UserProfileData({required this.name, required this.email});
|
||||
final String name;
|
||||
final String email;
|
||||
|
||||
UserProfileData copyWith({String? name, String? email}) {
|
||||
return UserProfileData(
|
||||
name: name ?? this.name,
|
||||
email: email ?? this.email,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 6. STREAM NOTIFIER - MUTABLE STATE FROM STREAM
|
||||
// ============================================================================
|
||||
|
||||
/// StreamNotifier for state that comes from a stream but can be modified
|
||||
/// Use for WebSocket connections with additional actions
|
||||
@riverpod
|
||||
class LiveChatNotifier extends _$LiveChatNotifier {
|
||||
@override
|
||||
Stream<List<String>> build() {
|
||||
// Return the stream
|
||||
return Stream.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(count) => ['Message $count'],
|
||||
);
|
||||
}
|
||||
|
||||
/// Send a new message
|
||||
Future<void> sendMessage(String message) async {
|
||||
// Send via WebSocket/API
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 7. PROVIDER DEPENDENCIES (COMPOSITION)
|
||||
// ============================================================================
|
||||
|
||||
/// Provider that depends on other providers
|
||||
@riverpod
|
||||
Future<String> dashboardData(Ref ref) async {
|
||||
// Watch other providers
|
||||
final userData = await ref.watch(userDataProvider.future);
|
||||
final version = ref.watch(appVersionProvider);
|
||||
|
||||
return 'Dashboard for $userData on version $version';
|
||||
}
|
||||
|
||||
/// Provider that selectively watches for optimization
|
||||
/// Note: userProfileProvider is actually a Family provider (takes userId parameter)
|
||||
/// In real code, you would use the generated AsyncNotifier provider
|
||||
@riverpod
|
||||
String userDisplayName(Ref ref) {
|
||||
// Example: Watch a simple computed value based on other providers
|
||||
final version = ref.watch(appVersionProvider);
|
||||
|
||||
// In a real app, you would watch the UserProfileNotifier like:
|
||||
// final asyncProfile = ref.watch(userProfileNotifierProvider);
|
||||
// return asyncProfile.when(...)
|
||||
|
||||
return 'User on version $version';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 8. KEEP ALIVE vs AUTO DISPOSE
|
||||
// ============================================================================
|
||||
|
||||
/// By default, generated providers are autoDispose
|
||||
/// They clean up when no longer used
|
||||
@riverpod
|
||||
String autoDisposeExample(Ref ref) {
|
||||
// This provider will be disposed when no longer watched
|
||||
ref.onDispose(() {
|
||||
// Clean up resources
|
||||
});
|
||||
|
||||
return 'Auto disposed';
|
||||
}
|
||||
|
||||
/// Keep alive provider - never auto-disposes
|
||||
/// Use for global state, singletons, services
|
||||
@Riverpod(keepAlive: true)
|
||||
String keepAliveExample(Ref ref) {
|
||||
// This provider stays alive until the app closes
|
||||
return 'Kept alive';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 9. LIFECYCLE HOOKS
|
||||
// ============================================================================
|
||||
|
||||
/// Provider with lifecycle hooks
|
||||
@riverpod
|
||||
String lifecycleExample(Ref ref) {
|
||||
// Called when provider is first created
|
||||
ref.onDispose(() {
|
||||
// Clean up when provider is disposed
|
||||
print('Provider disposed');
|
||||
});
|
||||
|
||||
ref.onCancel(() {
|
||||
// Called when last listener is removed (before dispose)
|
||||
print('Last listener removed');
|
||||
});
|
||||
|
||||
ref.onResume(() {
|
||||
// Called when a new listener is added after onCancel
|
||||
print('New listener added');
|
||||
});
|
||||
|
||||
return 'Lifecycle example';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 10. INVALIDATION AND REFRESH
|
||||
// ============================================================================
|
||||
|
||||
/// Provider showing how to invalidate and refresh
|
||||
@riverpod
|
||||
class DataManager extends _$DataManager {
|
||||
@override
|
||||
Future<String> build() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return 'Initial data';
|
||||
}
|
||||
|
||||
/// Refresh this provider's data
|
||||
Future<void> refresh() async {
|
||||
// Method 1: Use ref.invalidateSelf()
|
||||
ref.invalidateSelf();
|
||||
|
||||
// Method 2: Manually update state
|
||||
state = const AsyncValue.loading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return 'Refreshed data';
|
||||
});
|
||||
}
|
||||
|
||||
/// Invalidate another provider
|
||||
void invalidateOther() {
|
||||
ref.invalidate(userDataProvider);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 11. ERROR HANDLING PATTERNS
|
||||
// ============================================================================
|
||||
|
||||
/// Provider with comprehensive error handling
|
||||
@riverpod
|
||||
class ErrorHandlingExample extends _$ErrorHandlingExample {
|
||||
@override
|
||||
Future<String> build() async {
|
||||
return await _fetchData();
|
||||
}
|
||||
|
||||
Future<String> _fetchData() async {
|
||||
try {
|
||||
// Simulate API call
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return 'Success';
|
||||
} catch (e) {
|
||||
// Errors are automatically caught by AsyncValue
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Update with error handling
|
||||
Future<void> updateData(String newData) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// AsyncValue.guard automatically catches errors
|
||||
state = await AsyncValue.guard(() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
return newData;
|
||||
});
|
||||
|
||||
// Handle the result
|
||||
state.when(
|
||||
data: (data) {
|
||||
// Success
|
||||
print('Updated successfully: $data');
|
||||
},
|
||||
loading: () {
|
||||
// Still loading
|
||||
},
|
||||
error: (error, stack) {
|
||||
// Handle error
|
||||
print('Update failed: $error');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// USAGE IN WIDGETS
|
||||
// ============================================================================
|
||||
|
||||
/*
|
||||
|
||||
// 1. Watching a simple provider
|
||||
final version = ref.watch(appVersionProvider);
|
||||
|
||||
// 2. Watching async provider
|
||||
final userData = ref.watch(userDataProvider);
|
||||
userData.when(
|
||||
data: (data) => Text(data),
|
||||
loading: () => CircularProgressIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
|
||||
// 3. Watching with family (parameters)
|
||||
final profile = ref.watch(userProfileProvider('user123'));
|
||||
|
||||
// 4. Watching state from Notifier
|
||||
final count = ref.watch(counterProvider);
|
||||
|
||||
// 5. Calling Notifier methods
|
||||
ref.read(counterProvider.notifier).increment();
|
||||
|
||||
// 6. Watching AsyncNotifier state
|
||||
final profileState = ref.watch(userProfileNotifierProvider);
|
||||
|
||||
// 7. Calling AsyncNotifier methods
|
||||
await ref.read(userProfileNotifierProvider.notifier).updateName('New Name');
|
||||
|
||||
// 8. Selective watching for optimization
|
||||
final userName = ref.watch(
|
||||
userProfileNotifierProvider.select((async) => async.value?.name),
|
||||
);
|
||||
|
||||
// 9. Invalidating a provider
|
||||
ref.invalidate(userDataProvider);
|
||||
|
||||
// 10. Refreshing a provider
|
||||
ref.refresh(userDataProvider);
|
||||
|
||||
// 11. Pattern matching (Dart 3.0+)
|
||||
final profileState = ref.watch(userProfileNotifierProvider);
|
||||
switch (profileState) {
|
||||
case AsyncData(:final value):
|
||||
return Text(value.name);
|
||||
case AsyncError(:final error):
|
||||
return Text('Error: $error');
|
||||
case AsyncLoading():
|
||||
return CircularProgressIndicator();
|
||||
}
|
||||
|
||||
*/
|
||||
1155
lib/core/providers/provider_examples.g.dart
Normal file
1155
lib/core/providers/provider_examples.g.dart
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user