Files
worker/RIVERPOD_SETUP.md
Phuoc Nguyen 628c81ce13 runable
2025-10-17 17:22:28 +07:00

627 lines
14 KiB
Markdown

# Riverpod 3.0 Setup - Worker Flutter App
## Overview
This document provides a complete guide to the Riverpod 3.0 state management setup for the Worker Flutter app.
## What's Configured
### 1. Dependencies (pubspec.yaml)
**Production Dependencies:**
- `flutter_riverpod: ^3.0.0` - Main Riverpod package
- `riverpod_annotation: ^3.0.0` - Annotations for code generation
**Development Dependencies:**
- `build_runner: ^2.4.11` - Code generation runner
- `riverpod_generator: ^3.0.0` - Generates provider code from annotations
- `riverpod_lint: ^3.0.0` - Riverpod-specific linting rules
- `custom_lint: ^0.7.0` - Required for riverpod_lint
### 2. Build Configuration (build.yaml)
Configured to generate code for:
- `**_provider.dart` files
- Files in `**/providers/` directories
- Files in `**/notifiers/` directories
### 3. Analysis Options (analysis_options.yaml)
Configured with:
- Custom lint plugin enabled
- Exclusion of generated files (*.g.dart, *.freezed.dart)
- Riverpod-specific lint rules
- Comprehensive code quality rules
### 4. App Initialization (main.dart)
Wrapped with `ProviderScope`:
```dart
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
```
## Directory Structure
```
lib/core/providers/
├── connectivity_provider.dart # Network connectivity monitoring
├── provider_examples.dart # Comprehensive Riverpod 3.0 examples
└── README.md # Provider architecture documentation
```
## Quick Start
### 1. Install Dependencies
```bash
flutter pub get
```
### 2. Generate Provider Code
```bash
# One-time generation
dart run build_runner build --delete-conflicting-outputs
# Watch mode (auto-regenerates on file changes)
dart run build_runner watch -d
```
### 3. Use the Setup Script
```bash
chmod +x scripts/setup_riverpod.sh
./scripts/setup_riverpod.sh
```
## Core Providers
### Connectivity Provider
Location: `/lib/core/providers/connectivity_provider.dart`
**Purpose:** Monitor network connectivity status across the app.
**Providers Available:**
1. **connectivityProvider** - Connectivity instance
```dart
final connectivity = ref.watch(connectivityProvider);
```
2. **connectivityStreamProvider** - Real-time connectivity stream
```dart
final status = ref.watch(connectivityStreamProvider);
status.when(
data: (status) => Text('Status: $status'),
loading: () => CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
);
```
3. **currentConnectivityProvider** - One-time connectivity check
```dart
final status = await ref.read(currentConnectivityProvider.future);
```
4. **isOnlineProvider** - Boolean online/offline stream
```dart
final isOnline = ref.watch(isOnlineProvider);
```
**Usage Example:**
```dart
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final connectivityState = ref.watch(connectivityStreamProvider);
return connectivityState.when(
data: (status) {
if (status == ConnectivityStatus.offline) {
return OfflineBanner();
}
return OnlineContent();
},
loading: () => LoadingIndicator(),
error: (error, _) => ErrorWidget(error),
);
}
}
```
## Riverpod 3.0 Key Features
### 1. @riverpod Annotation (Code Generation)
The modern, recommended approach:
```dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'my_provider.g.dart';
// Simple value
@riverpod
String greeting(GreetingRef ref) => 'Hello';
// Async value
@riverpod
Future<User> user(UserRef ref, String id) async {
return await fetchUser(id);
}
// Mutable state
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
```
### 2. Unified Ref Type
No more separate `FutureProviderRef`, `StreamProviderRef`, etc. - just `Ref`:
```dart
@riverpod
Future<String> example(ExampleRef ref) async {
ref.watch(provider1);
ref.read(provider2);
ref.listen(provider3, (prev, next) {});
}
```
### 3. Family as Function Parameters
```dart
// Simple parameter
@riverpod
Future<User> user(UserRef ref, String id) async {
return await fetchUser(id);
}
// Multiple parameters with named, optional, defaults
@riverpod
Future<List<Post>> posts(
PostsRef ref, {
required String userId,
int page = 1,
int limit = 20,
String? category,
}) async {
return await fetchPosts(userId, page, limit, category);
}
// Usage
ref.watch(userProvider('user123'));
ref.watch(postsProvider(userId: 'user123', page: 2));
```
### 4. AutoDispose vs KeepAlive
```dart
// AutoDispose (default) - cleaned up when not watched
@riverpod
String autoExample(AutoExampleRef ref) => 'Auto disposed';
// KeepAlive - stays alive until app closes
@Riverpod(keepAlive: true)
String keepExample(KeepExampleRef ref) => 'Kept alive';
```
### 5. ref.mounted Check
New in Riverpod 3.0 - check if provider is still alive after async operations:
```dart
@riverpod
class DataManager extends _$DataManager {
@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';
}
}
```
### 6. AsyncValue.guard() for Error Handling
```dart
@riverpod
class UserProfile extends _$UserProfile {
@override
Future<User> build() async => await fetchUser();
Future<void> update(String name) async {
state = const AsyncValue.loading();
// AsyncValue.guard catches errors automatically
state = await AsyncValue.guard(() async {
return await updateUser(name);
});
}
}
```
## Provider Patterns
### 1. Simple Provider (Immutable Value)
```dart
@riverpod
String appVersion(AppVersionRef ref) => '1.0.0';
@riverpod
int pointsMultiplier(PointsMultiplierRef ref) {
final tier = ref.watch(userTierProvider);
return tier == 'diamond' ? 3 : 2;
}
```
### 2. FutureProvider (Async Data)
```dart
@riverpod
Future<User> currentUser(CurrentUserRef ref) async {
final token = await ref.watch(authTokenProvider.future);
return await fetchUser(token);
}
```
### 3. StreamProvider (Real-time Data)
```dart
@riverpod
Stream<List<Message>> chatMessages(ChatMessagesRef ref, String roomId) {
return ref.watch(webSocketProvider).messages(roomId);
}
```
### 4. Notifier (Mutable State)
```dart
@riverpod
class Cart extends _$Cart {
@override
List<CartItem> build() => [];
void addItem(Product product) {
state = [...state, CartItem.fromProduct(product)];
}
void removeItem(String id) {
state = state.where((item) => item.id != id).toList();
}
void clear() {
state = [];
}
}
```
### 5. AsyncNotifier (Async Mutable State)
```dart
@riverpod
class UserProfile extends _$UserProfile {
@override
Future<User> build() async {
return await ref.read(userRepositoryProvider).getCurrentUser();
}
Future<void> updateName(String name) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final updated = await ref.read(userRepositoryProvider).updateName(name);
return updated;
});
}
Future<void> refresh() async {
ref.invalidateSelf();
}
}
```
### 6. StreamNotifier (Stream Mutable State)
```dart
@riverpod
class LiveChat extends _$LiveChat {
@override
Stream<List<Message>> build(String roomId) {
return ref.watch(chatServiceProvider).messagesStream(roomId);
}
Future<void> sendMessage(String text) async {
await ref.read(chatServiceProvider).send(roomId, text);
}
}
```
## Usage in Widgets
### ConsumerWidget
```dart
class ProductList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final products = ref.watch(productsProvider);
return products.when(
data: (list) => ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ProductCard(list[index]),
),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorView(error),
);
}
}
```
### ConsumerStatefulWidget
```dart
class OrderPage extends ConsumerStatefulWidget {
@override
ConsumerState<OrderPage> createState() => _OrderPageState();
}
class _OrderPageState extends ConsumerState<OrderPage> {
@override
void initState() {
super.initState();
// Can use ref in all lifecycle methods
Future.microtask(
() => ref.read(ordersProvider.notifier).loadOrders(),
);
}
@override
Widget build(BuildContext context) {
final orders = ref.watch(ordersProvider);
return OrderList(orders);
}
}
```
### Consumer (Optimization)
```dart
Column(
children: [
const StaticHeader(),
Consumer(
builder: (context, ref, child) {
final count = ref.watch(cartCountProvider);
return CartBadge(count);
},
),
],
)
```
## Performance Optimization
### Use .select() to Watch Specific Fields
```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 with AsyncValue
final userName = ref.watch(
userProfileProvider.select((async) => async.value?.name),
);
```
### Provider Composition
```dart
@riverpod
Future<Dashboard> dashboard(DashboardRef ref) async {
// Depend on other providers
final user = await ref.watch(userProvider.future);
final stats = await ref.watch(statsProvider.future);
final orders = await ref.watch(recentOrdersProvider.future);
return Dashboard(
user: user,
stats: stats,
recentOrders: orders,
);
}
```
## Testing
### Unit Testing Providers
```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 fetches data', () async {
final container = ProviderContainer();
addTearDown(container.dispose);
final user = await container.read(userProvider.future);
expect(user.name, 'John Doe');
});
```
### Widget Testing
```dart
testWidgets('displays user name', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userProvider.overrideWith((ref) => User(name: 'Test User')),
],
child: MaterialApp(home: UserScreen()),
),
);
expect(find.text('Test User'), findsOneWidget);
});
```
### Mocking Providers
```dart
testWidgets('handles loading state', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userProvider.overrideWith((ref) {
return Future.delayed(
Duration(seconds: 10),
() => User(name: 'Test'),
);
}),
],
child: MaterialApp(home: UserScreen()),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
```
## Linting
### Run Riverpod Lint
```bash
# Check for Riverpod-specific issues
dart run custom_lint
# Auto-fix issues
dart run custom_lint --fix
```
### Riverpod Lint Rules Enabled
- `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 (don't use ref.read in build)
- `avoid_manual_providers_as_generated_provider_dependency` - Use generated providers
- `functional_ref` - Proper ref usage
- `notifier_build` - Proper Notifier implementation
## Common Issues & Solutions
### Issue 1: Generated files not found
**Solution:**
```bash
dart run build_runner build --delete-conflicting-outputs
```
### Issue 2: Provider not updating
**Solution:** Check if you're using `ref.watch()` not `ref.read()` in build method.
### Issue 3: Memory leaks
**Solution:** Use autoDispose (default) for providers that should clean up. Only use keepAlive for global state.
### Issue 4: Too many rebuilds
**Solution:** Use `.select()` to watch specific fields instead of entire objects.
## Migration from Riverpod 2.x
### StateNotifierProvider → Notifier
```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++;
}
```
### Provider.family → Function 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);
}
```
## Examples
Comprehensive examples are available in:
- `/lib/core/providers/provider_examples.dart` - All Riverpod 3.0 patterns
- `/lib/core/providers/connectivity_provider.dart` - Real-world connectivity monitoring
## 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](./lib/core/providers/provider_examples.dart)
## Next Steps
1. Run `flutter pub get` to install dependencies
2. Run `dart run build_runner watch -d` to start code generation
3. Create feature-specific providers in `lib/features/*/presentation/providers/`
4. Follow the patterns in `provider_examples.dart`
5. Use connectivity_provider as a reference for real-world implementation
## Support
For questions or issues:
1. Check provider_examples.dart for patterns
2. Review the Riverpod documentation
3. Run custom_lint to catch common mistakes
4. Use ref.watch() in build methods, ref.read() in event handlers