I’m refactoring my Flutter application to fully adopt Riverpod for dependency injection and state management. However, I’m struggling with how to simplify dependency access without relying on GetIt or manual singleton instances.
Example Context
I have a Logger dependency that I currently register like this (with GetIt):
final loggerConfig = LoggerConfig(
logSeverity: environment.tryGetString('LOG_LEVEL'),
);
GetIt.I.registerSingleton<LoggerContract>(Logger(loggerConfig));
// Usage
final logger = GetIt.I.get<LoggerContract>();
logger.log('Message');
I’m trying to transition this to Riverpod and eliminate GetIt, but I face two main issues:
-
Accessing the logger is verbose with Riverpod:
final logger = ref.read(loggerProvider); logger.log('Message');
I have to inject the ref into every class that needs the logger, which becomes repetitive and harder to manage as the project grows.
-
I want to avoid singleton manual instances like this:
class AppContainer { static final ProviderContainer container = ProviderContainer(); } LoggerContract get logger => AppContainer.container.read(loggerProvider);
This approach works, but feels against Riverpod’s philosophy of dependency injection and reactivity.
Question
- What is the best practice in Riverpod to simplify dependency access (e.g., for a logger) without creating a singleton or falling back to a service locator?
- Are there idiomatic ways in Riverpod to access providers globally or reduce boilerplate code for dependencies used frequently across the app?
I’m looking for an approach that adheres to Riverpod’s principles and keeps the code scalable and testable.