# Riverpod 3.0 Alignment Analysis ## Executive Summary The Conduit codebase is **well-aligned** with Riverpod 3.0 best practices. The project has already migrated to the new API and is using code generation in key areas. However, there are opportunities for improvement to achieve **full consistency** and leverage all Riverpod 3.0 features. **Overall Grade: B+ (85/100)** ✅ **Strengths:** - Already using Riverpod 3.0 packages - No legacy providers (`StateProvider`, `StateNotifierProvider`, `ChangeNotifierProvider`) - Using `@Riverpod` annotation with code generation for complex providers - Proper use of `Notifier` and `AsyncNotifier` classes - Good use of `ref.mounted` checks in async operations - Proper `keepAlive` management for singleton providers ⚠️ **Areas for Improvement:** - Mixed approach (code generation vs manual providers) - Missing `riverpod_lint` for enhanced static analysis - Some providers could benefit from code generation - Inconsistent provider organization --- ## Current State Analysis ### 1. Package Dependencies ✅ ```yaml dependencies: flutter_riverpod: ^3.0.0 # ✅ Correct riverpod_annotation: ^3.0.0 # ✅ Correct dev_dependencies: riverpod_generator: ^3.0.0 # ✅ Correct riverpod_lint: NOT PRESENT # ⚠️ Missing ``` ### 2. Provider Patterns #### ✅ **Good: Code Generation Pattern** Found in: `lib/core/auth/auth_state_manager.dart`, `lib/core/providers/app_providers.dart` ```dart @Riverpod(keepAlive: true) class AuthStateManager extends _$AuthStateManager { @override Future build() async { await _initialize(); return _current; } // ... methods } ``` **Benefits:** - Type-safe - Automatic provider generation - Better refactoring support - Family and autoDispose modifiers handled automatically #### ⚠️ **Mixed: Manual NotifierProvider Pattern** Found in: `lib/core/providers/app_providers.dart`, `lib/features/chat/providers/chat_providers.dart` ```dart // Manual declaration final themeModeProvider = NotifierProvider( ThemeModeNotifier.new, ); class ThemeModeNotifier extends Notifier { late final OptimizedStorageService _storage; @override ThemeMode build() { _storage = ref.watch(optimizedStorageServiceProvider); final storedMode = _storage.getThemeMode(); // ... return ThemeMode.system; } void setTheme(ThemeMode mode) { state = mode; _storage.setThemeMode(mode.toString()); } } ``` **Issues:** - Inconsistent with code generation approach - More boilerplate - Harder to add modifiers (family, autoDispose) later ### 3. Ref.mounted Usage ✅ **Good usage found in multiple files:** ```dart // lib/core/providers/app_providers.dart if (!ref.mounted) return; // lib/core/services/settings_service.dart if (!ref.mounted) { return; } ``` **Recommendation:** Continue this pattern and apply it more broadly. ### 4. Analysis Options ⚠️ Current `analysis_options.yaml` is missing Riverpod-specific lints: ```yaml include: package:flutter_lints/flutter.yaml linter: rules: avoid_print: true ``` **Missing:** - `riverpod_lint` custom lints - Provider-specific rules --- ## Detailed Recommendations ### 🔴 **Priority 1: Add riverpod_lint** **Impact:** High | **Effort:** Low | **Risk:** None Add the `riverpod_lint` package to catch common Riverpod mistakes at compile time. #### Changes Required: **1. Update `pubspec.yaml`:** ```yaml dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 build_runner: ^2.7.1 freezed: ^3.2.0 json_serializable: ^6.11.1 flutter_native_splash: ^2.4.6 riverpod_generator: ^3.0.0 riverpod_lint: ^3.0.0 # ADD THIS custom_lint: ^0.8.0 # REQUIRED FOR riverpod_lint ``` **2. Update `analysis_options.yaml`:** ```yaml include: package:flutter_lints/flutter.yaml analyzer: plugins: - custom_lint linter: rules: avoid_print: true ``` **3. Run:** ```bash dart pub get dart run custom_lint ``` **Benefits:** - Catches `ref` usage outside widgets/providers - Warns about missing `ref.mounted` checks - Detects provider misuse patterns - Automatic quick-fixes for common issues --- ### 🟡 **Priority 2: Standardize on Code Generation** **Impact:** Medium | **Effort:** Medium | **Risk:** Low Convert manual `NotifierProvider` declarations to use `@riverpod` annotation for consistency. #### Files to Refactor: 1. **`lib/core/providers/app_providers.dart`** - `themeModeProvider` / `ThemeModeNotifier` - `localeProvider` / `LocaleNotifier` - `selectedModelProvider` / `SelectedModelNotifier` - `isManualModelSelectionProvider` / `IsManualModelSelectionNotifier` - `searchQueryProvider` / `SearchQueryNotifier` - `activeConversationProvider` / `ActiveConversationNotifier` - `reviewerModeProvider` / `ReviewerModeNotifier` 2. **`lib/features/chat/providers/chat_providers.dart`** - `chatMessagesProvider` / `ChatMessagesNotifier` - `isLoadingConversationProvider` / `IsLoadingConversationNotifier` - `prefilledInputTextProvider` / `PrefilledInputTextNotifier` - `inputFocusTriggerProvider` / `InputFocusTriggerNotifier` - `composerHasFocusProvider` / `ComposerFocusNotifier` - Multiple other simple notifiers #### Example Refactoring: **Before (Manual):** ```dart final themeModeProvider = NotifierProvider( ThemeModeNotifier.new, ); class ThemeModeNotifier extends Notifier { 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 (Code Generation):** ```dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'app_providers.g.dart'; @riverpod class ThemeMode extends _$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()); } } // Usage changes from: // ref.watch(themeModeProvider) // ref.read(themeModeProvider.notifier).setTheme(mode) // To: // ref.watch(themeModeProvider) // ref.read(themeModeProvider.notifier).setTheme(mode) // (Same API!) ``` **Benefits:** - Consistent codebase - Less boilerplate - Better IDE support - Easier to add `family` or `autoDispose` modifiers later --- ### 🟡 **Priority 3: Optimize Provider Families** **Impact:** Medium | **Effort:** Low | **Risk:** None Some `FutureProvider.family` can benefit from better caching and disposal strategies. #### Example: `loadConversationProvider` **Current:** ```dart final loadConversationProvider = FutureProvider.family(( ref, conversationId, ) async { final api = ref.watch(apiServiceProvider); if (api == null) { throw Exception('No API service available'); } // ... }); ``` **Recommendation:** ```dart @riverpod Future loadConversation( LoadConversationRef ref, String conversationId, ) async { final api = ref.watch(apiServiceProvider); if (api == null) { throw Exception('No API service available'); } // Automatic disposal when no longer used // Better caching behavior final conversation = await api.getConversation(conversationId); return conversation; } ``` --- ### 🟢 **Priority 4: Improve AsyncValue Handling** **Impact:** Low | **Effort:** Low | **Risk:** None Some providers use `maybeWhen` where `when` might be more appropriate for exhaustive handling. #### Example from `lib/features/auth/providers/unified_auth_providers.dart`: **Current:** ```dart final isAuthenticatedProvider2 = Provider((ref) { final authState = ref.watch(authStateManagerProvider); return authState.maybeWhen( data: (state) => state.isAuthenticated, orElse: () => false, ); }); ``` **Better:** ```dart final isAuthenticatedProvider2 = Provider((ref) { final authState = ref.watch(authStateManagerProvider); return authState.when( data: (state) => state.isAuthenticated, loading: () => false, error: (_, __) => false, ); }); ``` **Benefits:** - Explicit handling of all states - Better error visibility - Compiler-enforced exhaustiveness --- ### 🟢 **Priority 5: Add Provider Documentation** **Impact:** Low | **Effort:** Low | **Risk:** None Add dartdoc comments to providers explaining their purpose and refresh behavior. **Example:** ```dart /// Manages the current theme mode (light/dark/system). /// /// Persists the selection using [OptimizedStorageService]. /// This provider is kept alive for the app lifetime. @riverpod class ThemeMode extends _$ThemeMode { // ... } /// The currently active conversation being displayed in the chat view. /// /// Set to `null` when no conversation is active (e.g., on the home screen). /// Watching this provider will trigger a rebuild when the conversation changes. @riverpod class ActiveConversation extends _$ActiveConversation { // ... } ``` --- ## Migration Plan ### Phase 1: Low-Risk Improvements (Week 1) 1. ✅ Add `riverpod_lint` and `custom_lint` packages 2. ✅ Update `analysis_options.yaml` 3. ✅ Run linter and fix any immediate issues 4. ✅ Add provider documentation **Estimated Time:** 4-6 hours **Risk Level:** 🟢 Low ### Phase 2: Code Generation Migration (Week 2-3) 1. ⚠️ Convert simple `Notifier` classes to `@riverpod` (low risk) - Start with leaf nodes (no dependents) - Test thoroughly after each conversion 2. ⚠️ Convert `Provider` declarations to `@riverpod` functions 3. ⚠️ Regenerate code with `build_runner` 4. ⚠️ Update all references (IDE should help with renames) **Estimated Time:** 16-24 hours **Risk Level:** 🟡 Medium ### Phase 3: Optimization (Week 4) 1. 🔵 Optimize `FutureProvider.family` patterns 2. 🔵 Improve `AsyncValue` handling 3. 🔵 Add caching strategies where appropriate 4. 🔵 Review and optimize `keepAlive` usage **Estimated Time:** 8-12 hours **Risk Level:** 🟢 Low --- ## Testing Strategy ### Before Each Change: ```bash # 1. Ensure all tests pass flutter test # 2. Run code generation dart run build_runner build --delete-conflicting-outputs # 3. Run custom lint dart run custom_lint # 4. Analyze code flutter analyze # 5. Manual testing on at least 2 platforms (iOS + Android) flutter run ``` ### After Migration: 1. **Functional Testing:** - Test all auth flows (login, logout, token refresh) - Test chat functionality - Test settings persistence - Test navigation flows 2. **Performance Testing:** - Monitor build times - Check app startup time - Profile provider rebuilds (DevTools) 3. **Regression Testing:** - Run full test suite - Test on physical devices - Check for memory leaks --- ## Code Examples ### Example 1: Simple Notifier Migration **File:** `lib/features/chat/providers/chat_providers.dart` **Before:** ```dart final isLoadingConversationProvider = NotifierProvider( IsLoadingConversationNotifier.new, ); class IsLoadingConversationNotifier extends Notifier { @override bool build() => false; void set(bool value) => state = value; } ``` **After:** ```dart @riverpod class IsLoadingConversation extends _$IsLoadingConversation { @override bool build() => false; void set(bool value) => state = value; } // Usage remains the same: // ref.watch(isLoadingConversationProvider) // ref.read(isLoadingConversationProvider.notifier).set(true) ``` ### Example 2: Provider to Function **Before:** ```dart final serverConfigsProvider = FutureProvider>((ref) async { final storage = ref.watch(optimizedStorageServiceProvider); return storage.getServerConfigs(); }); ``` **After:** ```dart @riverpod Future> serverConfigs(ServerConfigsRef ref) async { final storage = ref.watch(optimizedStorageServiceProvider); return storage.getServerConfigs(); } // Usage remains identical: // ref.watch(serverConfigsProvider) // ref.read(serverConfigsProvider.future) ``` ### Example 3: Keep Alive Provider **Before:** ```dart @Riverpod(keepAlive: true) class AuthStateManager extends _$AuthStateManager { // ... } ``` **After (same - already correct!):** ```dart @Riverpod(keepAlive: true) class AuthStateManager extends _$AuthStateManager { // ... } ``` --- ## Potential Issues & Solutions ### Issue 1: Breaking Changes **Problem:** Renaming providers may break existing code. **Solution:** 1. Use IDE's "Find and Replace" with regex 2. Create deprecation aliases during transition 3. Update incrementally, one provider at a time ```dart // Temporary compatibility @Deprecated('Use themeModeProvider instead') final oldThemeModeProvider = themeModeProvider; ``` ### Issue 2: Complex State Logic **Problem:** Some `Notifier` classes have complex initialization. **Solution:** Code generation supports complex logic—no changes needed! ```dart @riverpod class ChatMessages extends _$ChatMessages { StreamSubscription? _messageStream; ProviderSubscription? _conversationListener; // ... all existing fields and initialization work fine @override List build() { if (!_initialized) { _initialized = true; _conversationListener = ref.listen(activeConversationProvider, /* ... */); } // ... existing logic } } ``` ### Issue 3: Build Runner Performance **Problem:** Code generation might slow down development. **Solution:** 1. Use `watch` mode during development: ```bash dart run build_runner watch --delete-conflicting-outputs ``` 2. Exclude generated files from version control (already done) 3. Consider CI/CD optimizations for parallel builds --- ## Performance Considerations ### Current Performance: ✅ Good The codebase already uses: - `keepAlive` for singleton providers - `ref.mounted` checks for async operations - Proper disposal in `ref.onDispose` ### After Migration: ✅ Better Code generation will: - Reduce runtime overhead (compile-time generation) - Enable better tree-shaking - Improve IDE performance with generated code **Expected Impact:** - Build time: +5-10 seconds (one-time per build) - Runtime performance: Neutral to +2% faster - Memory usage: Neutral - Developer experience: Significantly better --- ## Conflict with AGENTS.md Rules ### Current Rule in AGENTS.md: ```markdown ### State Management * **Built-in Solutions:** Prefer Flutter's built-in state management solutions. Do not use a third-party package unless explicitly requested. ``` ### Recommendation: Update AGENTS.md The project has **already adopted Riverpod**, which contradicts this rule. The rule should be updated to reflect the current architecture: ```markdown ### State Management * **Riverpod:** This project uses Riverpod 3.0 for state management. * **Code Generation:** Prefer using `@riverpod` annotation with code generation for all new providers. * **Notifier Classes:** Use `Notifier` and `AsyncNotifier` for mutable state. * **Provider Functions:** Use `@riverpod` functions for computed/derived state. * **Keep Alive:** Use `@Riverpod(keepAlive: true)` for singletons and app-wide state. * **Ref.mounted:** Always check `ref.mounted` before state updates in async operations. ``` --- ## Resources ### Official Riverpod 3.0 Documentation - [Riverpod 3.0 Migration Guide](https://riverpod.dev/docs/3.0_migration) - [Code Generation Guide](https://riverpod.dev/docs/concepts/about_code_generation) - [Riverpod Lint Rules](https://riverpod.dev/docs/concepts/about_riverpod_lint) ### Community Resources - [Riverpod 3.0 Announcement](https://medium.com/@ishuprabhakar/riverpod-3-0-1c0e247bfb2f) - [Migration Tutorial](https://codewithandrea.com/articles/flutter-state-management-riverpod/) --- ## Conclusion The Conduit codebase is **in good shape** regarding Riverpod 3.0 alignment. The main improvements are: 1. **Add `riverpod_lint`** for better static analysis (Priority 1) 2. **Standardize on code generation** for consistency (Priority 2) 3. **Optimize provider patterns** where applicable (Priority 3) **Total Estimated Effort:** 28-42 hours **Risk Level:** 🟡 Medium **Expected Benefits:** High (better maintainability, consistency, developer experience) The migration can be done incrementally with minimal risk if following the phased approach outlined above.