Files
retail/RIVERPOD_DI_MIGRATION.md
Phuoc Nguyen bdaf0b96c5 fix
2025-10-10 17:36:10 +07:00

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!