This commit is contained in:
Phuoc Nguyen
2025-10-17 17:22:28 +07:00
parent 2125e85d40
commit 628c81ce13
86 changed files with 31339 additions and 1710 deletions

View 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

View 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)

View 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
/// ),
/// );
/// ```

View 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';

View 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();
}
*/

File diff suppressed because it is too large Load Diff