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
|
||||
Reference in New Issue
Block a user