316 lines
7.3 KiB
Markdown
316 lines
7.3 KiB
Markdown
# 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:**
|
|
```dart
|
|
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:**
|
|
```dart
|
|
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:**
|
|
```dart
|
|
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:**
|
|
```dart
|
|
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:**
|
|
```dart
|
|
class RetailApp extends ConsumerWidget {
|
|
const RetailApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return MaterialApp(/* ... */);
|
|
}
|
|
}
|
|
```
|
|
|
|
**After:**
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
// 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
|
|
|
|
1. **No Manual Registration**: Providers are automatically available
|
|
2. **Type Safety**: Compile-time type checking
|
|
3. **Dependency Graph**: Riverpod manages dependencies automatically
|
|
4. **Testability**: Easy to override providers in tests
|
|
5. **Code Generation**: Auto-generates provider code
|
|
6. **No Circular Dependencies**: Proper lifecycle management
|
|
7. **Singleton Management**: Use `keepAlive: true` for singletons
|
|
|
|
---
|
|
|
|
## GetIt Files (Now Unused)
|
|
|
|
These files are no longer needed but kept for reference:
|
|
|
|
- `lib/core/di/service_locator.dart` - Old GetIt setup
|
|
- `lib/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
|
|
|
|
- [x] Create Riverpod providers for DioClient
|
|
- [x] Create Riverpod providers for SecureStorage
|
|
- [x] Create Riverpod providers for AuthRemoteDataSource
|
|
- [x] Create Riverpod providers for AuthRepository
|
|
- [x] Remove GetIt references from auth_provider.dart
|
|
- [x] Fix circular dependency in Auth.build()
|
|
- [x] Remove GetIt setup from main.dart
|
|
- [x] Initialize auth state in app.dart
|
|
- [x] Regenerate code with build_runner
|
|
- [x] Test compilation (0 errors)
|
|
|
|
---
|
|
|
|
## Build Status
|
|
|
|
```
|
|
✅ Errors: 0
|
|
✅ Warnings: 61 (info-level only)
|
|
✅ Build: SUCCESS
|
|
✅ Code Generation: COMPLETE
|
|
```
|
|
|
|
---
|
|
|
|
## Testing the App
|
|
|
|
1. **Run the app**:
|
|
```bash
|
|
flutter run
|
|
```
|
|
|
|
2. **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
|
|
|
|
1. **Riverpod providers replace GetIt** for dependency injection
|
|
2. **Use `keepAlive: true`** for singleton providers (DioClient, SecureStorage)
|
|
3. **Never call async operations in `build()`** - use separate initialization methods
|
|
4. **Initialize auth state in app.dart** using `addPostFrameCallback`
|
|
5. **All dependencies are managed through providers** - no manual registration needed
|
|
|
|
---
|
|
|
|
## Next Steps (Optional)
|
|
|
|
If you want to further clean up:
|
|
|
|
1. Delete unused GetIt files:
|
|
```bash
|
|
rm lib/core/di/service_locator.dart
|
|
rm lib/core/di/injection_container.dart
|
|
```
|
|
|
|
2. Remove GetIt from dependencies in `pubspec.yaml`:
|
|
```yaml
|
|
# Remove this line:
|
|
get_it: ^8.0.2
|
|
```
|
|
|
|
3. Run `flutter pub get` to update dependencies
|
|
|
|
---
|
|
|
|
**Status**: ✅ **MIGRATION COMPLETE - NO ERRORS**
|
|
|
|
The app now uses pure Riverpod for all dependency injection!
|