Provider

The most basic provider type for synchronous, immutable values. Use Provider when you have a value that never changes during the application's lifecycle.

When to Use Provider

Basic Syntax

Create a simple provider with the @riverpod annotation:

config.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'config.g.dart';

@riverpod
String appName(AppNameRef ref) {
  return 'Riverpod Gen Shortcuts';
}

@riverpod
int maxRetries(MaxRetriesRef ref) {
  return 3;
}

Service Provider

Provide service instances to your application:

services.dart
@riverpod
ApiService apiService(ApiServiceRef ref) {
  return ApiService(
    baseUrl: 'https://api.example.com',
    timeout: Duration(seconds: 30),
  );
}

@riverpod
UserRepository userRepository(UserRepositoryRef ref) {
  final apiService = ref.watch(apiServiceProvider);
  return UserRepository(apiService);
}

Computed Values

Create providers that depend on other providers:

computed_values.dart
@riverpod
String welcomeMessage(WelcomeMessageRef ref) {
  final appName = ref.watch(appNameProvider);
  final user = ref.watch(currentUserProvider);
  
  return 'Welcome to $appName, ${user.name}!';
}

@riverpod
bool isDarkMode(IsDarkModeRef ref) {
  final settings = ref.watch(appSettingsProvider);
  return settings.theme == AppTheme.dark;
}

Using Providers

Access provider values in your widgets:

my_widget.dart
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final appName = ref.watch(appNameProvider);
    final welcomeMsg = ref.watch(welcomeMessageProvider);
    
    return Column(
      children: [
        Text(appName, style: Theme.of(context).textTheme.headlineMedium),
        Text(welcomeMsg),
      ],
    );
  }
}

Provider Modifiers

Keep Alive

Prevent provider disposal:

expensive_service.dart
@Riverpod(keepAlive: true)
ExpensiveService expensiveService(ExpensiveServiceRef ref) {
  // This provider will never be disposed
  return ExpensiveService();
}

Dependencies

Explicitly declare provider dependencies:

data_repository.dart
@Riverpod(dependencies: [apiServiceProvider, configProvider])
DataRepository dataRepository(DataRepositoryRef ref) {
  final api = ref.watch(apiServiceProvider);
  final config = ref.watch(configProvider);
  
  return DataRepository(api, config);
}

Best Practices

Do

  • Use Provider for immutable values and services
  • Keep provider functions pure (no side effects)
  • Use meaningful names that describe the value
  • Group related providers in the same file

Don't

  • Use Provider for mutable state (use NotifierProvider instead)
  • Perform expensive operations in provider functions
  • Create providers inside build methods
  • Use providers for values that change frequently

Common Examples

Configuration Provider

app_config.dart
@riverpod
AppConfig appConfig(AppConfigRef ref) {
  return AppConfig(
    apiUrl: const String.fromEnvironment('API_URL', 
      defaultValue: 'https://api.example.com'
    ),
    enableAnalytics: bool.fromEnvironment('ENABLE_ANALYTICS'),
    debugMode: kDebugMode,
  );
}

Theme Provider

theme.dart
@riverpod
ThemeData lightTheme(LightThemeRef ref) {
  return ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber),
    fontFamily: 'Iosevka',
  );
}

@riverpod
ThemeData darkTheme(DarkThemeRef ref) {
  return ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.amber,
      brightness: Brightness.dark,
    ),
    fontFamily: 'Iosevka',
  );
}

Next Steps

Ready for mutable state? Check out NotifierProvider for state that can change over time, or FutureProvider for asynchronous operations.