Files
worker/lib/core/providers/QUICK_REFERENCE.md
Phuoc Nguyen 19d9a3dc2d update loaing
2025-12-02 18:09:20 +07:00

8.7 KiB

Riverpod 3.0 Quick Reference Card

File Structure

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'my_provider.g.dart';  // REQUIRED!

// Your providers here

Provider Types

1. Simple Value (Immutable)

@riverpod
String appName(AppNameRef ref) => 'Worker App';

// Usage
final name = ref.watch(appNameProvider);

2. Async Value (Future)

@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

@riverpod
Stream<Message> messages(MessagesRef ref) {
  return webSocket.messages;
}

// Usage
final messages = ref.watch(messagesProvider);

4. Mutable State (Notifier)

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

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

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

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

class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(myProvider);
    return Text(value);
  }
}

ConsumerStatefulWidget

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)

Consumer(
  builder: (context, ref, child) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  },
)

Ref Methods

ref.watch() - Use in build

// Rebuilds when value changes
final value = ref.watch(myProvider);

ref.read() - Use in callbacks

// One-time read, doesn't listen
onPressed: () {
  ref.read(myProvider.notifier).update();
}

ref.listen() - Side effects

ref.listen(authProvider, (prev, next) {
  if (next.isLoggedOut) {
    Navigator.of(context).pushReplacementNamed('/login');
  }
});

ref.invalidate() - Force refresh

ref.invalidate(userProvider);

ref.refresh() - Invalidate and read

final newValue = ref.refresh(userProvider);

AsyncValue Handling

.when()

asyncValue.when(
  data: (value) => Text(value),
  loading: () => const CustomLoadingIndicator(),
  error: (error, stack) => Text('Error: $error'),
);

Pattern Matching (Dart 3+)

switch (asyncValue) {
  case AsyncData(:final value):
    return Text(value);
  case AsyncError(:final error):
    return Text('Error: $error');
  case AsyncLoading():
    return const CustomLoadingIndicator();
}

Direct Checks

if (asyncValue.isLoading) return Loading();
if (asyncValue.hasError) return Error();
final data = asyncValue.value!;

Performance Optimization

Use .select()

// 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()

@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

@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

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

@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

# 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

# Check Riverpod issues
dart run custom_lint

# Auto-fix
dart run custom_lint --fix

Testing

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

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

Future<void> save() async {
  state = const AsyncValue.loading();
  state = await AsyncValue.guard(() => api.save());
}

Pagination

@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

@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