Files
iiEsaywebUIapp/RIVERPOD_PRIORITY2_PLAN.md
cogwheel0 f18d378c3c docs: add comprehensive Riverpod 3.0 migration documentation and Priority 1 implementation
Priority 1 (COMPLETE):
- Add riverpod_lint and custom_lint packages
- Update analysis_options.yaml with custom_lint plugin
- Update AGENTS.md with Riverpod 3.0 best practices
- Fix unsafe ref usage in modern_chat_input.dart
- All tests passing, zero breaking changes

Priority 2 (PLANNED):
- Complete migration plan for 39 providers (RIVERPOD_PRIORITY2_PLAN.md)
- Quick reference guide (RIVERPOD_PRIORITY2_QUICKREF.md)
- Progress tracker (RIVERPOD_PRIORITY2_TRACKER.md)
- Master documentation index (RIVERPOD_MIGRATION_INDEX.md)
- Analysis and summary documents

Documentation includes:
- Step-by-step migration examples
- 6-phase implementation plan (23-33 hours)
- Testing strategies and rollback procedures
- Risk assessment and mitigation
- Timeline and resource estimates
2025-09-30 14:27:50 +05:30

33 KiB

Priority 2 Migration Plan - Code Generation Standardization

Created: September 30, 2025
Status: 📋 Planning Phase
Estimated Effort: 16-24 hours
Risk Level: 🟡 Medium


Table of Contents

  1. Overview
  2. Providers to Migrate
  3. Migration Strategy
  4. Step-by-Step Instructions
  5. Testing Plan
  6. Rollback Plan

Overview

Goals

Convert all manual NotifierProvider and FutureProvider declarations to use @riverpod annotation for:

  • Consistency across the codebase
  • Less boilerplate
  • Better IDE support and type safety
  • Easier future modifications (family, autoDispose, etc.)
  • Automatic dependency tracking

Scope

Total Providers to Migrate: 39 providers across 7 files

Files Affected:

  1. lib/core/providers/app_providers.dart (26 providers)
  2. lib/features/chat/providers/chat_providers.dart (5 providers)
  3. lib/core/services/settings_service.dart (1 provider)
  4. lib/core/services/animation_service.dart (1 provider)
  5. lib/features/chat/services/message_batch_service.dart (1 provider)
  6. lib/features/prompts/providers/prompts_providers.dart (1 provider)
  7. lib/features/tools/providers/tools_providers.dart (1 provider)
  8. lib/shared/widgets/offline_indicator.dart (1 provider)
  9. lib/features/chat/services/voice_input_service.dart (1 provider)

Providers to Migrate

Category A: Simple Notifier Classes (Low Risk, High Priority)

These are straightforward state holders with minimal logic.

1. searchQueryProvider / SearchQueryNotifier

File: lib/core/providers/app_providers.dart (lines 1200-1209) Complexity: 🟢 Simple Usage Count: ~5-10 files Dependencies: None

Current:

final searchQueryProvider = NotifierProvider<SearchQueryNotifier, String>(
  SearchQueryNotifier.new,
);

class SearchQueryNotifier extends Notifier<String> {
  @override
  String build() => '';

  void set(String query) => state = query;
}

After:

@riverpod
class SearchQuery extends _$SearchQuery {
  @override
  String build() => '';

  void set(String query) => state = query;
}

// Generated provider name: searchQueryProvider ✅ (same!)

Migration Notes:

  • Class renamed from SearchQueryNotifier to SearchQuery (convention)
  • Provider name stays identical
  • No usage changes required

2. selectedModelProvider / SelectedModelNotifier

File: lib/core/providers/app_providers.dart (lines 618-635) Complexity: 🟢 Simple Usage Count: ~15-20 files Dependencies: None

Current:

final selectedModelProvider = NotifierProvider<SelectedModelNotifier, Model?>(
  SelectedModelNotifier.new,
);

class SelectedModelNotifier extends Notifier<Model?> {
  @override
  Model? build() => null;

  void set(Model? model) => state = model;

  void clear() => state = null;
}

After:

@riverpod
class SelectedModel extends _$SelectedModel {
  @override
  Model? build() => null;

  void set(Model? model) => state = model;

  void clear() => state = null;
}

// Generated provider name: selectedModelProvider ✅

3. isManualModelSelectionProvider / IsManualModelSelectionNotifier

File: lib/core/providers/app_providers.dart (lines 623-642) Complexity: 🟢 Simple Usage Count: ~3-5 files Dependencies: None

Current:

final isManualModelSelectionProvider =
    NotifierProvider<IsManualModelSelectionNotifier, bool>(
      IsManualModelSelectionNotifier.new,
    );

class IsManualModelSelectionNotifier extends Notifier<bool> {
  @override
  bool build() => false;

  void set(bool value) => state = value;
}

After:

@riverpod
class IsManualModelSelection extends _$IsManualModelSelection {
  @override
  bool build() => false;

  void set(bool value) => state = value;
}

// Generated provider name: isManualModelSelectionProvider ✅

4. reviewerModeProvider / ReviewerModeNotifier

File: lib/core/providers/app_providers.dart (lines 1415-1430) Complexity: 🟢 Simple (has storage dependency) Usage Count: ~5-8 files Dependencies: optimizedStorageServiceProvider

Current:

final reviewerModeProvider = NotifierProvider<ReviewerModeNotifier, bool>(
  ReviewerModeNotifier.new,
);

class ReviewerModeNotifier extends Notifier<bool> {
  late final OptimizedStorageService _storage;

  @override
  bool build() {
    _storage = ref.watch(optimizedStorageServiceProvider);
    return _storage.getReviewerMode();
  }

  void set(bool value) {
    state = value;
    _storage.setReviewerMode(value);
  }
}

After:

@riverpod
class ReviewerMode extends _$ReviewerMode {
  late final OptimizedStorageService _storage;

  @override
  bool build() {
    _storage = ref.watch(optimizedStorageServiceProvider);
    return _storage.getReviewerMode();
  }

  void set(bool value) {
    state = value;
    _storage.setReviewerMode(value);
  }
}

// Generated provider name: reviewerModeProvider ✅

5. batchModeProvider / BatchModeNotifier

File: lib/features/chat/services/message_batch_service.dart (lines 532-540) Complexity: 🟢 Simple Usage Count: ~3-5 files Dependencies: None


6. reducedMotionProvider / ReducedMotionNotifier

File: lib/core/services/animation_service.dart (lines 211-225) Complexity: 🟢 Simple (has storage dependency) Usage Count: ~5-10 files Dependencies: optimizedStorageServiceProvider


Category B: Storage-Backed Notifiers (Low-Medium Risk)

These manage persistent state with storage dependencies.

7. themeModeProvider / ThemeModeNotifier

File: lib/core/providers/app_providers.dart (lines 71-95) Complexity: 🟡 Medium Usage Count: ~10-15 files Dependencies: optimizedStorageServiceProvider

Current:

final themeModeProvider = NotifierProvider<ThemeModeNotifier, ThemeMode>(
  ThemeModeNotifier.new,
);

class ThemeModeNotifier extends Notifier<ThemeMode> {
  late final OptimizedStorageService _storage;

  @override
  ThemeMode build() {
    _storage = ref.watch(optimizedStorageServiceProvider);
    final storedMode = _storage.getThemeMode();
    if (storedMode != null) {
      return ThemeMode.values.firstWhere(
        (e) => e.toString() == storedMode,
        orElse: () => ThemeMode.system,
      );
    }
    return ThemeMode.system;
  }

  void setTheme(ThemeMode mode) {
    state = mode;
    _storage.setThemeMode(mode.toString());
  }
}

After:

@riverpod
class AppThemeMode extends _$AppThemeMode {
  late final OptimizedStorageService _storage;

  @override
  ThemeMode build() {
    _storage = ref.watch(optimizedStorageServiceProvider);
    final storedMode = _storage.getThemeMode();
    if (storedMode != null) {
      return ThemeMode.values.firstWhere(
        (e) => e.toString() == storedMode,
        orElse: () => ThemeMode.system,
      );
    }
    return ThemeMode.system;
  }

  void setTheme(ThemeMode mode) {
    state = mode;
    _storage.setThemeMode(mode.toString());
  }
}

// Generated provider name: appThemeModeProvider

⚠️ Important:

  • Class renamed from ThemeModeNotifier to AppThemeMode to avoid conflict with Flutter's ThemeMode enum
  • Provider name changes from themeModeProvider to appThemeModeProvider
  • Requires bulk find-replace across codebase

Migration Command:

# Find all usages first
grep -r "themeModeProvider" lib/ --exclude="*.g.dart" | wc -l

# Replace (use IDE refactoring or sed)
find lib -type f -name "*.dart" ! -name "*.g.dart" -exec sed -i '' 's/themeModeProvider/appThemeModeProvider/g' {} +

8. localeProvider / LocaleNotifier

File: lib/core/providers/app_providers.dart (lines 98-119) Complexity: 🟢 Simple (async method) Usage Count: ~8-12 files Dependencies: optimizedStorageServiceProvider

Current:

final localeProvider = NotifierProvider<LocaleNotifier, Locale?>(
  LocaleNotifier.new,
);

class LocaleNotifier extends Notifier<Locale?> {
  late final OptimizedStorageService _storage;

  @override
  Locale? build() {
    _storage = ref.watch(optimizedStorageServiceProvider);
    final code = _storage.getLocaleCode();
    if (code != null && code.isNotEmpty) {
      return Locale(code);
    }
    return null; // system default
  }

  Future<void> setLocale(Locale? locale) async {
    state = locale;
    await _storage.setLocaleCode(locale?.languageCode);
  }
}

After:

@riverpod
class AppLocale extends _$AppLocale {
  late final OptimizedStorageService _storage;

  @override
  Locale? build() {
    _storage = ref.watch(optimizedStorageServiceProvider);
    final code = _storage.getLocaleCode();
    if (code != null && code.isNotEmpty) {
      return Locale(code);
    }
    return null; // system default
  }

  Future<void> setLocale(Locale? locale) async {
    state = locale;
    await _storage.setLocaleCode(locale?.languageCode);
  }
}

// Generated provider name: appLocaleProvider

⚠️ Important:

  • Class renamed from LocaleNotifier to AppLocale to avoid potential conflicts
  • Provider name changes from localeProvider to appLocaleProvider
  • Requires bulk find-replace across codebase

9. appSettingsProvider / AppSettingsNotifier

File: lib/core/services/settings_service.dart (lines 407-500+) Complexity: 🔴 Complex (large class with many methods) Usage Count: ~20-30 files (high usage!) Dependencies: optimizedStorageServiceProvider, apiServiceProvider

Migration Strategy:

  • ⚠️ Save for last due to high complexity and usage
  • Test extensively before committing
  • Consider splitting into smaller providers if possible

Category C: Chat-Specific Notifiers (Medium Risk)

10. isLoadingConversationProvider / IsLoadingConversationNotifier

File: lib/features/chat/providers/chat_providers.dart (lines 30-57) Complexity: 🟢 Simple Usage Count: ~5-8 files

After:

@riverpod
class IsLoadingConversation extends _$IsLoadingConversation {
  @override
  bool build() => false;

  void set(bool value) => state = value;
}

// Generated provider name: isLoadingConversationProvider ✅

11. prefilledInputTextProvider / PrefilledInputTextNotifier

File: lib/features/chat/providers/chat_providers.dart (lines 36-66) Complexity: 🟢 Simple Usage Count: ~3-5 files

After:

@riverpod
class PrefilledInputText extends _$PrefilledInputText {
  @override
  String? build() => null;

  void set(String? value) => state = value;

  void clear() => state = null;
}

// Generated provider name: prefilledInputTextProvider ✅

12. inputFocusTriggerProvider / InputFocusTriggerNotifier

File: lib/features/chat/providers/chat_providers.dart (lines 42-79) Complexity: 🟢 Simple Usage Count: ~3-5 files

After:

@riverpod
class InputFocusTrigger extends _$InputFocusTrigger {
  @override
  int build() => 0;

  void set(int value) => state = value;

  int increment() {
    final next = state + 1;
    state = next;
    return next;
  }
}

// Generated provider name: inputFocusTriggerProvider ✅

13. composerHasFocusProvider / ComposerFocusNotifier

File: lib/features/chat/providers/chat_providers.dart (lines 48-86) Complexity: 🟢 Simple Usage Count: ~3-5 files

After:

@riverpod
class ComposerHasFocus extends _$ComposerHasFocus {
  @override
  bool build() => false;

  void set(bool value) => state = value;
}

// Generated provider name: composerHasFocusProvider ✅

14. chatMessagesProvider / ChatMessagesNotifier

File: lib/features/chat/providers/chat_providers.dart (lines 24-2532) Complexity: 🔴 Very Complex (2500+ lines!) Usage Count: ~15-20 files (high usage!) Dependencies: Many (socket, API, storage, etc.)

Migration Strategy:

  • ⚠️ Save for last due to extreme complexity
  • Consider refactoring into smaller providers first
  • Extensive testing required

Category D: FutureProvider Functions (Low-Medium Risk)

These are stateless async computations that can be easily converted.

15. serverConfigsProvider

File: lib/core/providers/app_providers.dart (lines 122-125) Complexity: 🟢 Simple Usage Count: ~5-10 files

Current:

final serverConfigsProvider = FutureProvider<List<ServerConfig>>((ref) async {
  final storage = ref.watch(optimizedStorageServiceProvider);
  return storage.getServerConfigs();
});

After:

@riverpod
Future<List<ServerConfig>> serverConfigs(ServerConfigsRef ref) async {
  final storage = ref.watch(optimizedStorageServiceProvider);
  return storage.getServerConfigs();
}

// Generated provider name: serverConfigsProvider ✅

16. activeServerProvider

File: lib/core/providers/app_providers.dart (lines 127-141) Complexity: 🟢 Simple

After:

@riverpod
Future<ServerConfig?> activeServer(ActiveServerRef ref) async {
  final storage = ref.watch(optimizedStorageServiceProvider);
  final configs = await ref.watch(serverConfigsProvider.future);
  final activeId = await storage.getActiveServerId();

  if (activeId == null || configs.isEmpty) return null;

  for (final config in configs) {
    if (config.id == activeId) {
      return config;
    }
  }

  return null;
}

// Generated provider name: activeServerProvider ✅

17. currentUserProvider

File: lib/core/providers/app_providers.dart (lines 549-568) Complexity: 🟢 Simple

After:

@riverpod
Future<User?> currentUser(CurrentUserRef ref) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) return null;

  try {
    final user = await api.getCurrentUser();
    return user;
  } catch (e) {
    DebugLogger.error('current-user-failed', scope: 'user', error: e);
    return null;
  }
}

// Generated provider name: currentUserProvider ✅

18. modelsProvider

File: lib/core/providers/app_providers.dart (lines 570-615) Complexity: 🟡 Medium (has caching logic)

After:

@riverpod
Future<List<Model>> models(ModelsRef ref) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) return [];

  try {
    final models = await api.getModels();
    DebugLogger.log('models-fetched', scope: 'models', data: {'count': models.length});
    return models;
  } catch (e) {
    DebugLogger.error('models-fetch-failed', scope: 'models', error: e);
    return [];
  }
}

// Generated provider name: modelsProvider ✅

19. conversationsProvider

File: lib/core/providers/app_providers.dart (lines 722-990+) Complexity: 🔴 Complex (has custom caching, listeners) Usage Count: ~10-15 files

Migration Strategy:

  • ⚠️ Medium priority (moderate complexity)
  • Keep caching behavior intact
  • Test conversation list loading thoroughly

20. defaultModelProvider

File: lib/core/providers/app_providers.dart (lines 1020-1050) Complexity: 🟢 Simple


21. userSettingsProvider

File: lib/core/providers/app_providers.dart (lines 1450-1465) Complexity: 🟢 Simple

After:

@riverpod
Future<UserSettings> userSettings(UserSettingsRef ref) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) {
    return UserSettings.empty();
  }

  try {
    return await api.getUserSettings();
  } catch (e) {
    DebugLogger.error('user-settings-fetch-failed', scope: 'settings', error: e);
    return UserSettings.empty();
  }
}

// Generated provider name: userSettingsProvider ✅

22. conversationSuggestionsProvider

File: lib/core/providers/app_providers.dart (lines 1468-1480) Complexity: 🟢 Simple


23. userPermissionsProvider

File: lib/core/providers/app_providers.dart (lines 1483-1500) Complexity: 🟢 Simple


24. foldersProvider

File: lib/core/providers/app_providers.dart (lines 1530-1555) Complexity: 🟢 Simple


25. userFilesProvider

File: lib/core/providers/app_providers.dart (lines 1560-1575) Complexity: 🟢 Simple


26. knowledgeBasesProvider

File: lib/core/providers/app_providers.dart (lines 1605-1625) Complexity: 🟢 Simple


27. availableVoicesProvider

File: lib/core/providers/app_providers.dart (lines 1649-1665) Complexity: 🟢 Simple


28. imageModelsProvider

File: lib/core/providers/app_providers.dart (lines 1667-1680) Complexity: 🟢 Simple


Category E: Family Providers (Medium Risk)

29. loadConversationProvider (family)

File: lib/core/providers/app_providers.dart (lines 995-1015) Complexity: 🟡 Medium Usage Count: ~5-10 files

Current:

final loadConversationProvider = FutureProvider.family<Conversation, String>((
  ref,
  conversationId,
) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) {
    throw Exception('No API service available');
  }
  // ... fetch logic
});

After:

@riverpod
Future<Conversation> loadConversation(
  LoadConversationRef ref,
  String conversationId,
) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) {
    throw Exception('No API service available');
  }
  // ... fetch logic
}

// Usage stays the same:
// ref.watch(loadConversationProvider(conversationId))

30. serverSearchProvider (family)

File: lib/core/providers/app_providers.dart (lines 1212-1250) Complexity: 🟢 Simple

After:

@riverpod
Future<List<Conversation>> serverSearch(
  ServerSearchRef ref,
  String query,
) async {
  if (query.trim().isEmpty) {
    return [];
  }

  final api = ref.watch(apiServiceProvider);
  if (api == null) return [];

  try {
    final trimmedQuery = query.trim();
    DebugLogger.log('server-search', scope: 'search', data: {'query': trimmedQuery});
    final results = await api.searchConversations(trimmedQuery);
    return results;
  } catch (e) {
    DebugLogger.error('server-search-failed', scope: 'search', error: e);
    return [];
  }
}

// Generated provider name: serverSearchProvider ✅

31. fileContentProvider (family)

File: lib/core/providers/app_providers.dart (lines 1579-1600) Complexity: 🟢 Simple

After:

@riverpod
Future<String> fileContent(
  FileContentRef ref,
  String fileId,
) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) return '';

  try {
    return await api.getFileContent(fileId);
  } catch (e) {
    DebugLogger.error('file-content-fetch-failed', scope: 'files', error: e);
    return '';
  }
}

// Generated provider name: fileContentProvider ✅

Category F: Feature-Specific Providers (Low Risk)

32. promptsListProvider

File: lib/features/prompts/providers/prompts_providers.dart (lines 6-15) Complexity: 🟢 Simple Usage Count: ~2-3 files

After:

@riverpod
Future<List<Prompt>> promptsList(PromptsListRef ref) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) return [];
  
  try {
    return await api.getPrompts();
  } catch (e) {
    return [];
  }
}

// Generated provider name: promptsListProvider ✅

33. toolsListProvider

File: lib/features/tools/providers/tools_providers.dart (lines 5-15) Complexity: 🟢 Simple Usage Count: ~2-3 files

After:

@riverpod
Future<List<Tool>> toolsList(ToolsListRef ref) async {
  final api = ref.watch(apiServiceProvider);
  if (api == null) return [];
  
  try {
    return await api.getTools();
  } catch (e) {
    return [];
  }
}

// Generated provider name: toolsListProvider ✅

34. voiceInputAvailableProvider

File: lib/features/chat/services/voice_input_service.dart (lines 325-330) Complexity: 🟢 Simple Usage Count: ~2-3 files


Category G: Private/Internal Providers (Low Risk)

35. _wasOfflineProvider / _WasOfflineNotifier

File: lib/shared/widgets/offline_indicator.dart (lines 55-65) Complexity: 🟢 Simple Usage Count: 1 file (internal)

Note: This is a private provider (starts with _). Can migrate but low priority.


36. _conversationsCacheTimestampProvider (internal)

File: lib/core/providers/app_providers.dart (lines 709-718) Complexity: 🟢 Simple Usage Count: 1-2 files (internal)

Note: Internal caching helper. Can migrate alongside conversationsProvider.


Migration Strategy

Phase 1: Simple Wins (Week 1, 4-6 hours)

Migrate simple, low-usage providers to build confidence and establish patterns.

Targets (10 providers):

  1. searchQueryProvider
  2. selectedModelProvider
  3. isManualModelSelectionProvider
  4. reviewerModeProvider
  5. batchModeProvider
  6. isLoadingConversationProvider
  7. prefilledInputTextProvider
  8. inputFocusTriggerProvider
  9. composerHasFocusProvider
  10. reducedMotionProvider

Process:

  1. Migrate one provider
  2. Run build_runner
  3. Test manually
  4. Commit
  5. Repeat

Exit Criteria:

  • All 10 providers migrated
  • All tests passing
  • No regressions in functionality

Phase 2: FutureProvider Functions (Week 2, 6-8 hours)

Convert simple async functions to @riverpod functions.

Targets (15 providers):

  1. serverConfigsProvider
  2. activeServerProvider
  3. currentUserProvider
  4. modelsProvider
  5. defaultModelProvider
  6. userSettingsProvider
  7. conversationSuggestionsProvider
  8. userPermissionsProvider
  9. foldersProvider
  10. userFilesProvider
  11. knowledgeBasesProvider
  12. availableVoicesProvider
  13. imageModelsProvider
  14. promptsListProvider
  15. toolsListProvider

Process:

  1. Migrate in batches of 3-5 providers
  2. Run build_runner after each batch
  3. Test affected features
  4. Commit batch

Phase 3: Family Providers (Week 2, 2-3 hours)

Convert family providers to @riverpod functions with parameters.

Targets (4 providers):

  1. loadConversationProvider
  2. serverSearchProvider
  3. fileContentProvider
  4. voiceInputAvailableProvider

Process:

  1. Migrate one at a time
  2. Pay attention to parameter types
  3. Test with different parameter values
  4. Commit individually

Phase 4: Storage-Backed Notifiers (Week 3, 4-6 hours)

Migrate providers that require provider name changes (breaking changes).

Targets (2 providers + bulk replace):

  1. ⚠️ themeModeProviderappThemeModeProvider
  2. ⚠️ localeProviderappLocaleProvider

Process:

  1. Migrate provider definition
  2. Run build_runner
  3. Find and replace all usages (use IDE refactoring)
  4. Run tests
  5. Manual testing on all platforms
  6. Commit with clear message about breaking change

Find/Replace Commands:

# ThemeMode migration
grep -r "themeModeProvider" lib/ --exclude="*.g.dart" | wc -l
find lib -type f -name "*.dart" ! -name "*.g.dart" -exec sed -i '' 's/themeModeProvider/appThemeModeProvider/g' {} +

# Locale migration
grep -r "localeProvider" lib/ --exclude="*.g.dart" | wc -l
find lib -type f -name "*.dart" ! -name "*.g.dart" -exec sed -i '' 's/localeProvider/appLocaleProvider/g' {} +

Phase 5: Complex Providers (Week 4, 6-8 hours)

Migrate complex, high-usage providers with extensive testing.

Targets (3 providers):

  1. 🔴 conversationsProvider (complex caching)
  2. 🔴 appSettingsProvider (large class, high usage)
  3. 🔴 chatMessagesProvider (extremely complex, 2500+ lines)

Process:

  1. Read the entire class to understand all dependencies
  2. Create a test plan covering all methods
  3. Migrate the provider
  4. Run build_runner
  5. Run all tests
  6. Manual testing of all affected features
  7. Commit with detailed migration notes

Extra Caution:

  • Test on physical devices (iOS + Android)
  • Check for memory leaks
  • Monitor performance
  • Have rollback plan ready

Phase 6: Internal/Private Providers (Optional, 1-2 hours)

Migrate internal providers for completeness.

Targets (2 providers):

  1. _wasOfflineProvider
  2. _conversationsCacheTimestampProvider

Process:

  • Low priority
  • Can be done as cleanup task
  • No external dependencies to worry about

Step-by-Step Instructions

For Each Provider Migration

Step 1: Prepare

# Ensure clean git state
git status

# Ensure packages are up to date
flutter pub get

# Run tests to establish baseline
flutter test

# (Optional) Find usage count
grep -r "providerName" lib/ --exclude="*.g.dart" | wc -l

Step 2: Migrate the Provider

For Notifier Classes:

  1. Add @riverpod annotation
  2. Change class name (remove Notifier suffix, handle conflicts)
  3. Extend _$ClassName
  4. Remove manual provider declaration
  5. Keep all methods unchanged

For FutureProvider Functions:

  1. Add @riverpod annotation
  2. Convert to function with Ref parameter
  3. Add family parameters as function parameters (if applicable)
  4. Keep logic unchanged

Step 3: Generate Code

dart run build_runner build --delete-conflicting-outputs

Watch for:

  • Build errors (fix immediately)
  • Generated file created (.g.dart)
  • Provider name in generated code (check if it matches expected)

Step 4: Update Usages (if needed)

For providers with name changes only:

# Example: themeModeProvider → appThemeModeProvider
find lib -type f -name "*.dart" ! -name "*.g.dart" -exec sed -i '' 's/themeModeProvider/appThemeModeProvider/g' {} +

Or use IDE:

  1. Right-click on provider name
  2. Refactor → Rename
  3. Enter new name
  4. Preview changes
  5. Apply

Step 5: Verify

# Check for compilation errors
flutter analyze

# Run linter
dart run custom_lint

# Run tests
flutter test

# Manual smoke test
flutter run

Step 6: Commit

git add .
git commit -m "refactor: migrate providerName to @riverpod code generation

- Converted ProviderClass to use @riverpod annotation
- Provider name remains: providerNameProvider
- No breaking changes
- Tests passing ✅"

For breaking changes:

git commit -m "refactor!: migrate themeModeProvider to @riverpod

BREAKING CHANGE: Provider renamed from themeModeProvider to appThemeModeProvider

- Converted ThemeModeNotifier to AppThemeMode
- Updated all usages across codebase (N files)
- Tests passing ✅"

Testing Plan

Per-Provider Testing

After migrating each provider, perform:

  1. Compilation Check

    flutter analyze
    
  2. Lint Check

    dart run custom_lint
    
  3. Unit Tests

    flutter test
    
  4. Manual Smoke Test

    • Launch app
    • Navigate to feature using the provider
    • Verify behavior is unchanged

Phase Testing

After completing each phase:

  1. Full Test Suite

    flutter test --coverage
    
  2. Integration Testing

    • Test all major user flows
    • Auth flow (login, logout, token refresh)
    • Chat flow (create, send, receive)
    • Settings (change theme, locale, models)
    • Navigation (all routes)
  3. Platform Testing

    • iOS simulator
    • Android emulator
    • (Optional) Web browser
    • (Optional) Physical devices
  4. Performance Check

    • App startup time
    • Navigation performance
    • Memory usage (DevTools)
    • Provider rebuild counts (DevTools)

Final Testing (After All Phases)

  1. Regression Testing

    • Run full test suite
    • Manual testing of all features
    • Test on multiple platforms
    • Check for memory leaks
  2. Code Quality

    • Run analyzer
    • Run linter
    • Check code coverage
    • Review generated files
  3. Documentation

    • Update README if needed
    • Update AGENTS.md if needed
    • Add migration notes to commits

Rollback Plan

Per-Provider Rollback

If a single provider migration causes issues:

# Revert the last commit
git revert HEAD

# Or reset to before migration
git reset --hard HEAD~1

# Regenerate code
dart run build_runner build --delete-conflicting-outputs

# Test
flutter test

Phase Rollback

If an entire phase needs to be rolled back:

# Find the commit before phase started
git log --oneline

# Reset to that commit
git reset --hard <commit-hash>

# Regenerate code
dart run build_runner build --delete-conflicting-outputs

# Test
flutter test

Emergency Rollback

If production issues arise after deployment:

  1. Immediate:

    # Revert to last known good state
    git revert <range-of-commits>
    git push
    
  2. Rebuild and Deploy:

    flutter clean
    flutter pub get
    dart run build_runner build --delete-conflicting-outputs
    flutter build <platform>
    # Deploy to stores
    

Risk Mitigation

Low-Risk Providers (Categories A, D, F)

Mitigation:

  • Migrate in small batches
  • Commit frequently
  • Test after each migration

Rollback:

  • Easy (single commit revert)

Medium-Risk Providers (Categories B, C, E)

Mitigation:

  • Migrate individually
  • Extensive manual testing
  • Update documentation

Rollback:

  • Straightforward (commit revert)
  • May need to update usages

High-Risk Providers (appSettingsProvider, chatMessagesProvider, conversationsProvider)

Mitigation:

  • Create detailed test plan
  • Test on multiple platforms
  • Have team review changes
  • Deploy to staging first
  • Monitor production closely

Rollback:

  • More complex (many dependencies)
  • May need coordinated revert
  • Keep backup branch

Success Criteria

Phase Completion

Each phase is complete when:

  • All targeted providers migrated
  • All tests passing
  • No analyzer/lint errors
  • Manual testing successful
  • Performance unchanged or improved
  • Changes committed with clear messages

Overall Completion

Priority 2 migration is complete when:

  • All 39 providers migrated to @riverpod
  • Codebase uses consistent provider pattern
  • All tests passing
  • No regressions identified
  • Documentation updated
  • Team trained on new patterns

Timeline

Estimated Schedule

Phase Providers Effort Timeline
Phase 1 10 simple notifiers 4-6 hours Week 1
Phase 2 15 future providers 6-8 hours Week 2
Phase 3 4 family providers 2-3 hours Week 2
Phase 4 2 storage notifiers 4-6 hours Week 3
Phase 5 3 complex providers 6-8 hours Week 4
Phase 6 2 private providers 1-2 hours Week 4
Total 36 providers 23-33 hours 4 weeks

Note: 3 providers (activeConversationProvider, socketConnectionStreamProvider, conversationStreamProvider) are already using @riverpod and don't need migration.

Recommended Pace:

  • 1-2 hours per day
  • 5-10 providers per week
  • Don't rush high-risk migrations

Resources

Documentation

Tools

  • VS Code Riverpod snippets
  • Android Studio Riverpod plugin
  • dart run build_runner watch (auto-regenerate)

Commands

# Build once
dart run build_runner build --delete-conflicting-outputs

# Watch mode (recommended during development)
dart run build_runner watch --delete-conflicting-outputs

# Clean generated files
flutter clean
dart run build_runner clean

# Run all checks
flutter analyze && dart run custom_lint && flutter test

Notes

Naming Conventions

Class Names:

  • Remove Notifier suffix
  • Use descriptive names
  • Avoid conflicts with existing types (e.g., AppThemeMode instead of ThemeMode)

Provider Names:

  • Auto-generated from class/function name
  • Camel case with Provider suffix
  • Examples:
    • Class SearchQuerysearchQueryProvider
    • Function serverConfigsserverConfigsProvider

Common Issues

Issue 1: "_$ClassName not found"

# Solution: Run build_runner
dart run build_runner build --delete-conflicting-outputs

Issue 2: "Provider name already exists"

// Solution: Rename the class to avoid conflicts
@riverpod
class AppThemeMode extends _$AppThemeMode { // ✅ Not 'ThemeMode'
  // ...
}

Issue 3: "Tests failing after migration"

# Solution: Check if provider name changed
# Update test imports and usages

Conclusion

This Priority 2 migration plan provides a systematic approach to standardizing all providers in the Conduit codebase to use Riverpod 3.0 code generation.

Key Principles:

  1. Incremental: Small, focused changes
  2. Safe: Test after every change
  3. Reversible: Clear commits, easy rollback
  4. Documented: Clear migration notes

Expected Benefits:

  • Consistent codebase
  • Better IDE support
  • Reduced boilerplate
  • Easier future modifications
  • Improved developer experience

Risk Level: 🟡 Medium (manageable with careful execution)

Recommendation: Proceed with Phase 1 to build confidence, then continue incrementally based on results.


Status: 📋 Ready for Implementation
Next Action: Begin Phase 1 migrations