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