Files
worker/lib/core/providers
Phuoc Nguyen 628c81ce13 runable
2025-10-17 17:22:28 +07:00
..
2025-10-17 17:22:28 +07:00
2025-10-17 17:22:28 +07:00
2025-10-17 17:22:28 +07:00
2025-10-17 17:22:28 +07:00
2025-10-17 17:22:28 +07:00
2025-10-17 17:22:28 +07:00

Riverpod 3.0 Provider Architecture

This directory contains core-level providers that are used across the application.

Directory Structure

lib/core/providers/
  ├── connectivity_provider.dart      # Network connectivity monitoring
  ├── provider_examples.dart          # Comprehensive Riverpod 3.0 examples
  └── README.md                       # This file

Setup Instructions

1. Install Dependencies

Dependencies are already configured in pubspec.yaml:

dependencies:
  flutter_riverpod: ^3.0.0
  riverpod_annotation: ^3.0.0

dev_dependencies:
  build_runner: ^2.4.11
  riverpod_generator: ^3.0.0
  riverpod_lint: ^3.0.0
  custom_lint: ^0.7.0

Run to install:

flutter pub get

2. Generate Code

Run code generation whenever you create or modify providers:

# Watch mode (auto-regenerates on changes)
dart run build_runner watch -d

# One-time build
dart run build_runner build -d

# Clean and rebuild
dart run build_runner build --delete-conflicting-outputs

3. Wrap App with ProviderScope

In main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

Riverpod 3.0 Key Concepts

@riverpod Annotation

The @riverpod annotation is the core of code generation. It automatically generates the appropriate provider type based on your function/class signature.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'my_provider.g.dart';

// Simple value
@riverpod
String myValue(MyValueRef ref) => 'Hello';

// Async value
@riverpod
Future<String> myAsync(MyAsyncRef ref) async => 'Hello';

// Stream
@riverpod
Stream<int> myStream(MyStreamRef ref) => Stream.value(1);

// Mutable state
@riverpod
class MyNotifier extends _$MyNotifier {
  @override
  int build() => 0;

  void increment() => state++;
}

// Async mutable state
@riverpod
class MyAsyncNotifier extends _$MyAsyncNotifier {
  @override
  Future<String> build() async => 'Initial';

  Future<void> update(String value) async {
    state = await AsyncValue.guard(() async => value);
  }
}

Provider Types (Auto-Generated)

  1. Simple Provider - Immutable value

    • Function returning T → Provider
  2. FutureProvider - Async value

    • Function returning Future → FutureProvider
  3. StreamProvider - Stream of values

    • Function returning Stream → StreamProvider
  4. NotifierProvider - Mutable state with methods

    • Class extending Notifier → NotifierProvider
  5. AsyncNotifierProvider - Async mutable state

    • Class extending AsyncNotifier → AsyncNotifierProvider
  6. StreamNotifierProvider - Stream mutable state

    • Class extending StreamNotifier → StreamNotifierProvider

Family (Parameters)

In Riverpod 3.0, family is just function parameters!

// Old way (Riverpod 2.x)
final userProvider = FutureProvider.family<User, String>((ref, id) async {
  return fetchUser(id);
});

// New way (Riverpod 3.0)
@riverpod
Future<User> user(UserRef ref, String id) async {
  return fetchUser(id);
}

// Multiple parameters (named, optional, defaults)
@riverpod
Future<List<Post>> posts(
  PostsRef ref, {
  required String userId,
  int page = 1,
  int limit = 20,
  String? category,
}) async {
  return fetchPosts(userId, page, limit, category);
}

// Usage
ref.watch(userProvider('user123'));
ref.watch(postsProvider(userId: 'user123', page: 2, category: 'tech'));

AutoDispose vs KeepAlive

// AutoDispose (default) - cleaned up when not used
@riverpod
String autoExample(AutoExampleRef ref) => 'Auto disposed';

// KeepAlive - stays alive until app closes
@Riverpod(keepAlive: true)
String keepExample(KeepExampleRef ref) => 'Kept alive';

Unified Ref Type

Riverpod 3.0 uses a single Ref type (no more FutureProviderRef, StreamProviderRef, etc.):

@riverpod
Future<String> example(ExampleRef ref) async {
  // All providers use the same ref type
  ref.watch(otherProvider);
  ref.read(anotherProvider);
  ref.listen(thirdProvider, (prev, next) {});
  ref.invalidate(fourthProvider);
}

ref.mounted Check

Always check ref.mounted after async operations in Notifiers:

@riverpod
class Example extends _$Example {
  @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';
  }
}

Error Handling with AsyncValue.guard()

@riverpod
class Data extends _$Data {
  @override
  Future<String> build() async => 'Initial';

  Future<void> update(String value) async {
    state = const AsyncValue.loading();

    // AsyncValue.guard catches errors automatically
    state = await AsyncValue.guard(() async {
      await api.update(value);
      return value;
    });
  }
}

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
  void initState() {
    super.initState();
    // ref is available in all lifecycle methods
  }

  @override
  Widget build(BuildContext context) {
    final value = ref.watch(myProvider);
    return Text(value);
  }
}

Consumer (for optimization)

Column(
  children: [
    const Text('Static'),
    Consumer(
      builder: (context, ref, child) {
        final count = ref.watch(counterProvider);
        return Text('$count');
      },
    ),
  ],
)

Best Practices

1. Use .select() for Optimization

// 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 - rebuilds only when async value has data
final userName = ref.watch(
  userProvider.select((async) => async.value?.name),
);

2. Provider Dependencies

@riverpod
Future<Dashboard> dashboard(DashboardRef ref) async {
  // Watch other providers
  final user = await ref.watch(userProvider.future);
  final posts = await ref.watch(postsProvider.future);

  return Dashboard(user: user, posts: posts);
}

3. Invalidation and Refresh

// In a widget or notifier
ref.invalidate(userProvider);  // Invalidate
ref.refresh(userProvider);     // Invalidate and re-read

4. Lifecycle Hooks

@riverpod
String example(ExampleRef ref) {
  ref.onDispose(() {
    // Clean up
  });

  ref.onCancel(() {
    // Last listener removed
  });

  ref.onResume(() {
    // New listener added after cancel
  });

  return 'value';
}

5. 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);
});

test('async provider', () async {
  final container = ProviderContainer();
  addTearDown(container.dispose);

  final value = await container.read(userProvider.future);
  expect(value.name, 'John');
});

Migration from Riverpod 2.x

StateNotifierProvider → NotifierProvider

// 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++;
}

FutureProvider.family → Function with Parameters

// 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);
}

Ref Types → Single Ref

// Old (2.x)
final provider = FutureProvider<String>((FutureProviderRef ref) async {
  return 'value';
});

// New (3.0)
@riverpod
Future<String> provider(ProviderRef ref) async {
  return 'value';
}

Common Patterns

See provider_examples.dart for comprehensive examples of:

  • Simple providers
  • Async providers (FutureProvider pattern)
  • Stream providers
  • Notifier (mutable state)
  • AsyncNotifier (async mutable state)
  • StreamNotifier
  • Family (parameters)
  • Provider composition
  • Error handling
  • Lifecycle hooks
  • Optimization with .select()

Riverpod Lint Rules

The project is configured with riverpod_lint for additional checks:

  • 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
  • avoid_manual_providers_as_generated_provider_dependency - Use generated providers
  • functional_ref - Proper ref usage
  • notifier_build - Proper Notifier implementation

Run linting:

dart run custom_lint

Resources