# 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: () => const CustomLoadingIndicator(),
  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: () => const CustomLoadingIndicator(),
  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 const CustomLoadingIndicator();
}
```

### 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
