7.3 KiB
Riverpod Dependency Injection Migration
Date: October 10, 2025 Status: ✅ COMPLETE
Problem
The authentication system was trying to use GetIt for dependency injection, causing the following error:
Bad state: GetIt: Object/factory with type AuthRepository is not registered inside GetIt.
Additionally, there was a circular dependency error in the auth provider:
Bad state: Tried to read the state of an uninitialized provider.
This generally means that have a circular dependency, and your provider end-up depending on itself.
Solution
Migrated from GetIt to pure Riverpod dependency injection. All dependencies are now managed through Riverpod providers.
Changes Made
1. Updated Auth Provider (lib/features/auth/presentation/providers/auth_provider.dart)
Before:
import '../../../../core/di/injection_container.dart';
@riverpod
AuthRepository authRepository(Ref ref) {
return sl<AuthRepository>(); // Using GetIt
}
@riverpod
class Auth extends _$Auth {
@override
AuthState build() {
_checkAuthStatus(); // Circular dependency - calling async in build
return const AuthState();
}
}
After:
import '../../../../core/network/dio_client.dart';
import '../../../../core/storage/secure_storage.dart';
import '../../data/datasources/auth_remote_datasource.dart';
import '../../data/repositories/auth_repository_impl.dart';
/// Provider for DioClient (singleton)
@Riverpod(keepAlive: true)
DioClient dioClient(Ref ref) {
return DioClient();
}
/// Provider for SecureStorage (singleton)
@Riverpod(keepAlive: true)
SecureStorage secureStorage(Ref ref) {
return SecureStorage();
}
/// Provider for AuthRemoteDataSource
@Riverpod(keepAlive: true)
AuthRemoteDataSource authRemoteDataSource(Ref ref) {
final dioClient = ref.watch(dioClientProvider);
return AuthRemoteDataSourceImpl(dioClient: dioClient);
}
/// Provider for AuthRepository
@Riverpod(keepAlive: true)
AuthRepository authRepository(Ref ref) {
final remoteDataSource = ref.watch(authRemoteDataSourceProvider);
final secureStorage = ref.watch(secureStorageProvider);
final dioClient = ref.watch(dioClientProvider);
return AuthRepositoryImpl(
remoteDataSource: remoteDataSource,
secureStorage: secureStorage,
dioClient: dioClient,
);
}
@riverpod
class Auth extends _$Auth {
@override
AuthState build() {
// Don't call async operations in build
return const AuthState();
}
/// Initialize auth state - call this on app start
Future<void> initialize() async {
// Auth initialization logic moved here
}
}
2. Removed GetIt Setup (lib/main.dart)
Before:
import 'core/di/service_locator.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
// Setup dependency injection
await setupServiceLocator(); // GetIt initialization
runApp(const ProviderScope(child: RetailApp()));
}
After:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
// Run the app with Riverpod (no GetIt needed - using Riverpod for DI)
runApp(const ProviderScope(child: RetailApp()));
}
3. Initialize Auth State on App Start (lib/app.dart)
Before:
class RetailApp extends ConsumerWidget {
const RetailApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(/* ... */);
}
}
After:
class RetailApp extends ConsumerStatefulWidget {
const RetailApp({super.key});
@override
ConsumerState<RetailApp> createState() => _RetailAppState();
}
class _RetailAppState extends ConsumerState<RetailApp> {
@override
void initState() {
super.initState();
// Initialize auth state on app start
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(authProvider.notifier).initialize();
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(/* ... */);
}
}
Dependency Injection Architecture
Provider Hierarchy
DioClient (singleton)
↓
SecureStorage (singleton)
↓
AuthRemoteDataSource (uses DioClient)
↓
AuthRepository (uses AuthRemoteDataSource, SecureStorage, DioClient)
↓
Auth State Notifier (uses AuthRepository)
Provider Usage
// Access DioClient
final dioClient = ref.read(dioClientProvider);
// Access SecureStorage
final secureStorage = ref.read(secureStorageProvider);
// Access AuthRepository
final authRepository = ref.read(authRepositoryProvider);
// Access Auth State
final authState = ref.watch(authProvider);
// Call Auth Methods
await ref.read(authProvider.notifier).login(email: '...', password: '...');
await ref.read(authProvider.notifier).logout();
Benefits of Riverpod DI
- No Manual Registration: Providers are automatically available
- Type Safety: Compile-time type checking
- Dependency Graph: Riverpod manages dependencies automatically
- Testability: Easy to override providers in tests
- Code Generation: Auto-generates provider code
- No Circular Dependencies: Proper lifecycle management
- Singleton Management: Use
keepAlive: truefor singletons
GetIt Files (Now Unused)
These files are no longer needed but kept for reference:
lib/core/di/service_locator.dart- Old GetIt setuplib/core/di/injection_container.dart- Old GetIt container
You can safely delete these files if GetIt is not used anywhere else in the project.
Migration Checklist
- Create Riverpod providers for DioClient
- Create Riverpod providers for SecureStorage
- Create Riverpod providers for AuthRemoteDataSource
- Create Riverpod providers for AuthRepository
- Remove GetIt references from auth_provider.dart
- Fix circular dependency in Auth.build()
- Remove GetIt setup from main.dart
- Initialize auth state in app.dart
- Regenerate code with build_runner
- Test compilation (0 errors)
Build Status
✅ Errors: 0
✅ Warnings: 61 (info-level only)
✅ Build: SUCCESS
✅ Code Generation: COMPLETE
Testing the App
-
Run the app:
flutter run -
Expected behavior:
- App starts and shows login page (if not authenticated)
- Login with valid credentials
- Token is saved and added to Dio headers automatically
- Navigate to Settings to see user profile
- Logout button works correctly
- After logout, back to login page
Key Takeaways
- Riverpod providers replace GetIt for dependency injection
- Use
keepAlive: truefor singleton providers (DioClient, SecureStorage) - Never call async operations in
build()- use separate initialization methods - Initialize auth state in app.dart using
addPostFrameCallback - All dependencies are managed through providers - no manual registration needed
Next Steps (Optional)
If you want to further clean up:
-
Delete unused GetIt files:
rm lib/core/di/service_locator.dart rm lib/core/di/injection_container.dart -
Remove GetIt from dependencies in
pubspec.yaml:# Remove this line: get_it: ^8.0.2 -
Run
flutter pub getto update dependencies
Status: ✅ MIGRATION COMPLETE - NO ERRORS
The app now uses pure Riverpod for all dependency injection!