# Migration Guide to Riverpod DI

This guide helps you migrate from other dependency injection solutions (GetIt, Provider, etc.) to Riverpod.

## From GetIt to Riverpod

### Before (GetIt)

```dart
// Setup
final getIt = GetIt.instance;

void setupDI() {
  // Core
  getIt.registerLazySingleton<SecureStorage>(() => SecureStorage());
  getIt.registerLazySingleton<ApiClient>(() => ApiClient(getIt()));

  // Auth
  getIt.registerLazySingleton<AuthRemoteDataSource>(
    () => AuthRemoteDataSourceImpl(getIt()),
  );
  getIt.registerLazySingleton<AuthRepository>(
    () => AuthRepositoryImpl(
      remoteDataSource: getIt(),
      secureStorage: getIt(),
    ),
  );
  getIt.registerLazySingleton<LoginUseCase>(
    () => LoginUseCase(getIt()),
  );
}

// Usage in widget
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final authRepo = getIt<AuthRepository>();
    final loginUseCase = getIt<LoginUseCase>();
    return Container();
  }
}
```

### After (Riverpod)

```dart
// Setup (in lib/core/di/providers.dart)
final secureStorageProvider = Provider<SecureStorage>((ref) {
  return SecureStorage();
});

final apiClientProvider = Provider<ApiClient>((ref) {
  final secureStorage = ref.watch(secureStorageProvider);
  return ApiClient(secureStorage);
});

final authRemoteDataSourceProvider = Provider<AuthRemoteDataSource>((ref) {
  final apiClient = ref.watch(apiClientProvider);
  return AuthRemoteDataSourceImpl(apiClient);
});

final authRepositoryProvider = Provider<AuthRepository>((ref) {
  final remoteDataSource = ref.watch(authRemoteDataSourceProvider);
  final secureStorage = ref.watch(secureStorageProvider);
  return AuthRepositoryImpl(
    remoteDataSource: remoteDataSource,
    secureStorage: secureStorage,
  );
});

final loginUseCaseProvider = Provider<LoginUseCase>((ref) {
  final repository = ref.watch(authRepositoryProvider);
  return LoginUseCase(repository);
});

// Usage in widget
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authRepo = ref.watch(authRepositoryProvider);
    final loginUseCase = ref.watch(loginUseCaseProvider);
    return Container();
  }
}
```

## Key Differences

| Aspect | GetIt | Riverpod |
|--------|-------|----------|
| Setup | Manual registration in setup function | Declarative provider definitions |
| Access | `getIt<Type>()` anywhere | `ref.watch(provider)` in widgets |
| Widget Base | `StatelessWidget` / `StatefulWidget` | `ConsumerWidget` / `ConsumerStatefulWidget` |
| Dependencies | Manual injection | Automatic via `ref.watch()` |
| Lifecycle | Manual disposal | Automatic disposal |
| Testing | Override with `getIt.registerFactory()` | Override with `ProviderScope` |
| Type Safety | Runtime errors if not registered | Compile-time errors |
| Reactivity | Manual with ChangeNotifier | Built-in with StateNotifier |

## Migration Steps

### Step 1: Wrap App with ProviderScope

```dart
// Before
void main() {
  setupDI();
  runApp(MyApp());
}

// After
void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}
```

### Step 2: Convert Widgets to ConsumerWidget

```dart
// Before
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

// After
class MyPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Container();
  }
}
```

### Step 3: Replace GetIt Calls

```dart
// Before
final useCase = getIt<LoginUseCase>();
final result = await useCase(request);

// After
final useCase = ref.watch(loginUseCaseProvider);
final result = await useCase(request);
```

### Step 4: Convert State Management

```dart
// Before (ChangeNotifier + Provider)
class AuthNotifier extends ChangeNotifier {
  bool _isAuthenticated = false;
  bool get isAuthenticated => _isAuthenticated;

  void login() {
    _isAuthenticated = true;
    notifyListeners();
  }
}

// Register
getIt.registerLazySingleton(() => AuthNotifier());

// Usage
final authNotifier = getIt<AuthNotifier>();
authNotifier.addListener(() {
  // Handle change
});

// After (StateNotifier + Riverpod)
class AuthNotifier extends StateNotifier<AuthState> {
  AuthNotifier() : super(AuthState.initial());

  void login() {
    state = state.copyWith(isAuthenticated: true);
  }
}

// Provider
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
  return AuthNotifier();
});

// Usage
final authState = ref.watch(authProvider);
// Widget automatically rebuilds on state change
```

## Common Patterns Migration

### Pattern 1: Singleton Service

```dart
// Before (GetIt)
getIt.registerLazySingleton<MyService>(() => MyService());

// After (Riverpod)
final myServiceProvider = Provider<MyService>((ref) {
  return MyService();
});
```

### Pattern 2: Factory (New Instance Each Time)

```dart
// Before (GetIt)
getIt.registerFactory<MyService>(() => MyService());

// After (Riverpod)
final myServiceProvider = Provider.autoDispose<MyService>((ref) {
  return MyService();
});
```

### Pattern 3: Async Initialization

```dart
// Before (GetIt)
final myServiceFuture = getIt.getAsync<MyService>();

// After (Riverpod)
final myServiceProvider = FutureProvider<MyService>((ref) async {
  final service = MyService();
  await service.initialize();
  return service;
});
```

### Pattern 4: Conditional Registration

```dart
// Before (GetIt)
if (isProduction) {
  getIt.registerLazySingleton<ApiClient>(
    () => ProductionApiClient(),
  );
} else {
  getIt.registerLazySingleton<ApiClient>(
    () => MockApiClient(),
  );
}

// After (Riverpod)
final apiClientProvider = Provider<ApiClient>((ref) {
  if (isProduction) {
    return ProductionApiClient();
  } else {
    return MockApiClient();
  }
});
```

## Testing Migration

### Before (GetIt)

```dart
void main() {
  setUp(() {
    // Clear and re-register
    getIt.reset();
    getIt.registerLazySingleton<AuthRepository>(
      () => MockAuthRepository(),
    );
  });

  test('test case', () {
    final repo = getIt<AuthRepository>();
    // Test
  });
}
```

### After (Riverpod)

```dart
void main() {
  test('test case', () {
    final container = ProviderContainer(
      overrides: [
        authRepositoryProvider.overrideWithValue(mockAuthRepository),
      ],
    );

    final repo = container.read(authRepositoryProvider);
    // Test

    container.dispose();
  });
}
```

## Widget Testing Migration

### Before (GetIt + Provider)

```dart
testWidgets('widget test', (tester) async {
  // Setup mocks
  getIt.reset();
  getIt.registerLazySingleton<AuthRepository>(
    () => mockAuthRepository,
  );

  await tester.pumpWidget(
    ChangeNotifierProvider(
      create: (_) => AuthNotifier(),
      child: MaterialApp(home: LoginPage()),
    ),
  );

  // Test
});
```

### After (Riverpod)

```dart
testWidgets('widget test', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        authRepositoryProvider.overrideWithValue(mockAuthRepository),
      ],
      child: MaterialApp(home: LoginPage()),
    ),
  );

  // Test
});
```

## State Management Migration

### From ChangeNotifier to StateNotifier

```dart
// Before
class CounterNotifier extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

// Usage
final counter = context.watch<CounterNotifier>();
Text('${counter.count}');

// After
class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() {
    state = state + 1;
  }
}

final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

// Usage
final count = ref.watch(counterProvider);
Text('$count');
```

## Benefits of Migration

### 1. Type Safety
```dart
// GetIt - Runtime error if not registered
final service = getIt<MyService>(); // May crash at runtime

// Riverpod - Compile-time error
final service = ref.watch(myServiceProvider); // Compile-time check
```

### 2. Automatic Disposal
```dart
// GetIt - Manual disposal
class MyWidget extends StatefulWidget {
  @override
  void dispose() {
    getIt<MyService>().dispose();
    super.dispose();
  }
}

// Riverpod - Automatic
final myServiceProvider = Provider.autoDispose<MyService>((ref) {
  final service = MyService();
  ref.onDispose(() => service.dispose());
  return service;
});
```

### 3. Easy Testing
```dart
// GetIt - Need to reset and re-register
setUp(() {
  getIt.reset();
  getIt.registerLazySingleton<MyService>(() => MockMyService());
});

// Riverpod - Simple override
final container = ProviderContainer(
  overrides: [
    myServiceProvider.overrideWithValue(mockMyService),
  ],
);
```

### 4. Better Developer Experience
- No need to remember to register dependencies
- No need to call setup function
- Auto-completion works better
- Compile-time safety
- Built-in DevTools support

## Common Pitfalls

### Pitfall 1: Using ref.watch() in callbacks

```dart
// ❌ Wrong
ElevatedButton(
  onPressed: () {
    final user = ref.watch(currentUserProvider); // Error!
    print(user);
  },
  child: Text('Print User'),
)

// ✅ Correct
ElevatedButton(
  onPressed: () {
    final user = ref.read(currentUserProvider);
    print(user);
  },
  child: Text('Print User'),
)
```

### Pitfall 2: Not using ConsumerWidget

```dart
// ❌ Wrong - StatelessWidget doesn't have ref
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final data = ref.watch(dataProvider); // Error: ref not available
    return Container();
  }
}

// ✅ Correct - Use ConsumerWidget
class MyPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final data = ref.watch(dataProvider);
    return Container();
  }
}
```

### Pitfall 3: Calling methods in build()

```dart
// ❌ Wrong - Causes infinite loop
@override
Widget build(BuildContext context, WidgetRef ref) {
  ref.read(authProvider.notifier).checkAuthStatus(); // Infinite loop!
  return Container();
}

// ✅ Correct - Call in initState
@override
void initState() {
  super.initState();
  Future.microtask(() {
    ref.read(authProvider.notifier).checkAuthStatus();
  });
}
```

### Pitfall 4: Not disposing ProviderContainer in tests

```dart
// ❌ Wrong - Memory leak
test('test case', () {
  final container = ProviderContainer();
  // Test
});

// ✅ Correct - Always dispose
test('test case', () {
  final container = ProviderContainer();
  addTearDown(container.dispose);
  // Test
});
```

## Incremental Migration Strategy

You can migrate gradually:

1. **Phase 1: Add Riverpod**
   - Add dependency
   - Wrap app with ProviderScope
   - Keep GetIt for now

2. **Phase 2: Migrate Core**
   - Create core providers
   - Migrate one feature at a time
   - Both systems can coexist

3. **Phase 3: Migrate Features**
   - Start with simplest feature
   - Test thoroughly
   - Move to next feature

4. **Phase 4: Remove GetIt**
   - Once all migrated
   - Remove GetIt setup
   - Remove GetIt dependency

## Checklist

- [ ] Added `flutter_riverpod` dependency
- [ ] Wrapped app with `ProviderScope`
- [ ] Created `lib/core/di/providers.dart`
- [ ] Defined all providers
- [ ] Converted widgets to `ConsumerWidget`
- [ ] Replaced `getIt<T>()` with `ref.watch(provider)`
- [ ] Updated tests to use `ProviderContainer`
- [ ] Tested all features
- [ ] Removed GetIt setup code
- [ ] Removed GetIt dependency

## Need Help?

- Check [README.md](./README.md) for comprehensive guide
- See [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) for common patterns
- Review [ARCHITECTURE.md](./ARCHITECTURE.md) for understanding design
- Visit [Riverpod Documentation](https://riverpod.dev)

## Summary

Riverpod provides:
- ✅ Compile-time safety
- ✅ Better testing
- ✅ Automatic disposal
- ✅ Built-in state management
- ✅ No manual setup required
- ✅ Better developer experience
- ✅ Type-safe dependency injection
- ✅ Reactive by default

The migration effort is worth it for better code quality and maintainability!
