fix
This commit is contained in:
315
RIVERPOD_DI_MIGRATION.md
Normal file
315
RIVERPOD_DI_MIGRATION.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# 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!
|
||||
Reference in New Issue
Block a user