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
17 KiB
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
@Riverpodannotation with code generation for complex providers - Proper use of
NotifierandAsyncNotifierclasses - Good use of
ref.mountedchecks in async operations - Proper
keepAlivemanagement for singleton providers
⚠️ Areas for Improvement:
- Mixed approach (code generation vs manual providers)
- Missing
riverpod_lintfor enhanced static analysis - Some providers could benefit from code generation
- Inconsistent provider organization
Current State Analysis
1. Package Dependencies ✅
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
@Riverpod(keepAlive: true)
class AuthStateManager extends _$AuthStateManager {
@override
Future<AuthState> 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
// Manual declaration
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();
// ...
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:
// 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:
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: true
Missing:
riverpod_lintcustom 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:
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:
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
linter:
rules:
avoid_print: true
3. Run:
dart pub get
dart run custom_lint
Benefits:
- Catches
refusage outside widgets/providers - Warns about missing
ref.mountedchecks - 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:
-
lib/core/providers/app_providers.dartthemeModeProvider/ThemeModeNotifierlocaleProvider/LocaleNotifierselectedModelProvider/SelectedModelNotifierisManualModelSelectionProvider/IsManualModelSelectionNotifiersearchQueryProvider/SearchQueryNotifieractiveConversationProvider/ActiveConversationNotifierreviewerModeProvider/ReviewerModeNotifier
-
lib/features/chat/providers/chat_providers.dartchatMessagesProvider/ChatMessagesNotifierisLoadingConversationProvider/IsLoadingConversationNotifierprefilledInputTextProvider/PrefilledInputTextNotifierinputFocusTriggerProvider/InputFocusTriggerNotifiercomposerHasFocusProvider/ComposerFocusNotifier- Multiple other simple notifiers
Example Refactoring:
Before (Manual):
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 (Code Generation):
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
familyorautoDisposemodifiers 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:
final loadConversationProvider = FutureProvider.family<Conversation, String>((
ref,
conversationId,
) async {
final api = ref.watch(apiServiceProvider);
if (api == null) {
throw Exception('No API service available');
}
// ...
});
Recommendation:
@riverpod
Future<Conversation> 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:
final isAuthenticatedProvider2 = Provider<bool>((ref) {
final authState = ref.watch(authStateManagerProvider);
return authState.maybeWhen(
data: (state) => state.isAuthenticated,
orElse: () => false,
);
});
Better:
final isAuthenticatedProvider2 = Provider<bool>((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:
/// 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)
- ✅ Add
riverpod_lintandcustom_lintpackages - ✅ Update
analysis_options.yaml - ✅ Run linter and fix any immediate issues
- ✅ Add provider documentation
Estimated Time: 4-6 hours
Risk Level: 🟢 Low
Phase 2: Code Generation Migration (Week 2-3)
- ⚠️ Convert simple
Notifierclasses to@riverpod(low risk)- Start with leaf nodes (no dependents)
- Test thoroughly after each conversion
- ⚠️ Convert
Providerdeclarations to@riverpodfunctions - ⚠️ Regenerate code with
build_runner - ⚠️ Update all references (IDE should help with renames)
Estimated Time: 16-24 hours
Risk Level: 🟡 Medium
Phase 3: Optimization (Week 4)
- 🔵 Optimize
FutureProvider.familypatterns - 🔵 Improve
AsyncValuehandling - 🔵 Add caching strategies where appropriate
- 🔵 Review and optimize
keepAliveusage
Estimated Time: 8-12 hours
Risk Level: 🟢 Low
Testing Strategy
Before Each Change:
# 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:
-
Functional Testing:
- Test all auth flows (login, logout, token refresh)
- Test chat functionality
- Test settings persistence
- Test navigation flows
-
Performance Testing:
- Monitor build times
- Check app startup time
- Profile provider rebuilds (DevTools)
-
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:
final isLoadingConversationProvider =
NotifierProvider<IsLoadingConversationNotifier, bool>(
IsLoadingConversationNotifier.new,
);
class IsLoadingConversationNotifier extends Notifier<bool> {
@override
bool build() => false;
void set(bool value) => state = value;
}
After:
@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:
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();
}
// Usage remains identical:
// ref.watch(serverConfigsProvider)
// ref.read(serverConfigsProvider.future)
Example 3: Keep Alive Provider
Before:
@Riverpod(keepAlive: true)
class AuthStateManager extends _$AuthStateManager {
// ...
}
After (same - already correct!):
@Riverpod(keepAlive: true)
class AuthStateManager extends _$AuthStateManager {
// ...
}
Potential Issues & Solutions
Issue 1: Breaking Changes
Problem: Renaming providers may break existing code.
Solution:
- Use IDE's "Find and Replace" with regex
- Create deprecation aliases during transition
- Update incrementally, one provider at a time
// 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!
@riverpod
class ChatMessages extends _$ChatMessages {
StreamSubscription? _messageStream;
ProviderSubscription? _conversationListener;
// ... all existing fields and initialization work fine
@override
List<ChatMessage> build() {
if (!_initialized) {
_initialized = true;
_conversationListener = ref.listen(activeConversationProvider, /* ... */);
}
// ... existing logic
}
}
Issue 3: Build Runner Performance
Problem: Code generation might slow down development.
Solution:
- Use
watchmode during development:dart run build_runner watch --delete-conflicting-outputs - Exclude generated files from version control (already done)
- Consider CI/CD optimizations for parallel builds
Performance Considerations
Current Performance: ✅ Good
The codebase already uses:
keepAlivefor singleton providersref.mountedchecks 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:
### 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:
### 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
Community Resources
Conclusion
The Conduit codebase is in good shape regarding Riverpod 3.0 alignment. The main improvements are:
- Add
riverpod_lintfor better static analysis (Priority 1) - Standardize on code generation for consistency (Priority 2)
- 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.