/// Riverpod provider optimization utilities /// /// Features: /// - Granular rebuild prevention with .select() /// - Provider caching strategies /// - Debounced provider updates /// - Performance-optimized provider patterns import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'debouncer.dart'; /// Extension for optimized provider watching extension ProviderOptimizationExtensions on WidgetRef { /// Watch only a specific field to minimize rebuilds /// /// Example: /// ```dart /// final name = ref.watchField(userProvider, (user) => user.name); /// ``` T watchField(ProviderListenable provider, T Function(S) selector) { return watch(provider.select(selector)); } /// Watch multiple fields efficiently /// /// Example: /// ```dart /// final (name, age) = ref.watchFields( /// userProvider, /// (user) => (user.name, user.age), /// ); /// ``` T watchFields(ProviderListenable provider, T Function(S) selector) { return watch(provider.select(selector)); } /// Listen to provider only when condition is met /// /// Example: /// ```dart /// ref.listenWhen( /// userProvider, /// (prev, next) => prev?.id != next?.id, /// (prev, next) { /// // Handle change /// }, /// ); /// ``` void listenWhen( ProviderListenable provider, bool Function(T? previous, T next) condition, void Function(T? previous, T next) listener, { void Function(Object error, StackTrace stackTrace)? onError, }) { T? previous; listen( provider, (prev, next) { if (condition(prev, next)) { listener(prev, next); previous = next; } }, onError: onError, ); } } /// Debounced state notifier for performance optimization abstract class DebouncedStateNotifier extends StateNotifier { final Debouncer _debouncer; DebouncedStateNotifier( super.state, { int debounceDuration = 300, }) : _debouncer = Debouncer(milliseconds: debounceDuration); /// Update state with debouncing void updateDebounced(T newState) { _debouncer.run(() { if (mounted) { state = newState; } }); } /// Update state immediately (bypass debouncing) void updateImmediate(T newState) { _debouncer.cancel(); if (mounted) { state = newState; } } @override void dispose() { _debouncer.dispose(); super.dispose(); } } /// Cached async value to prevent unnecessary rebuilds class CachedAsyncValue { final AsyncValue _value; final DateTime _timestamp; final Duration _cacheDuration; CachedAsyncValue( this._value, { Duration cacheDuration = const Duration(minutes: 5), }) : _timestamp = DateTime.now(), _cacheDuration = cacheDuration; AsyncValue get value => _value; bool get isExpired { return DateTime.now().difference(_timestamp) > _cacheDuration; } bool get isValid { return !isExpired && _value is AsyncData; } } /// Provider cache manager class ProviderCacheManager { static final Map _cache = {}; /// Get cached value or compute new one static AsyncValue getOrCompute({ required String key, required AsyncValue Function() compute, Duration cacheDuration = const Duration(minutes: 5), }) { final cached = _cache[key] as CachedAsyncValue?; if (cached != null && cached.isValid) { return cached.value; } final value = compute(); _cache[key] = CachedAsyncValue(value, cacheDuration: cacheDuration); return value; } /// Invalidate specific cache entry static void invalidate(String key) { _cache.remove(key); } /// Clear all cache static void clear() { _cache.clear(); } /// Clean expired cache entries static void cleanExpired() { _cache.removeWhere((key, value) => value.isExpired); } } /// Optimized family provider cache class FamilyProviderCache { final Map _cache = {}; final int _maxSize; FamilyProviderCache({int maxSize = 50}) : _maxSize = maxSize; T? get(Arg arg) => _cache[arg]; void set(Arg arg, T value) { // Simple LRU: remove oldest if cache is full if (_cache.length >= _maxSize) { final firstKey = _cache.keys.first; _cache.remove(firstKey); } _cache[arg] = value; } void invalidate(Arg arg) { _cache.remove(arg); } void clear() { _cache.clear(); } int get size => _cache.length; } /// Performance-optimized state notifier mixin mixin PerformanceOptimizedNotifier on StateNotifier { /// Track rebuild count in debug mode int _rebuildCount = 0; @override set state(T value) { if (kDebugMode) { _rebuildCount++; if (_rebuildCount % 10 == 0) { debugPrint( '⚠️ ${runtimeType.toString()} has been rebuilt $_rebuildCount times', ); } } super.state = value; } /// Update state only if changed void updateIfChanged(T newState) { if (state != newState) { state = newState; } } /// Update part of state efficiently void updatePart(S Function(T) selector, S newValue) { if (selector(state) != newValue) { // This requires implementing proper state copying // Subclasses should override this throw UnimplementedError('updatePart must be implemented in subclass'); } } } /// Selector helper for complex state class StateSelector { final T state; const StateSelector(this.state); /// Select a field from state S select(S Function(T) selector) => selector(state); /// Select multiple fields R selectAll(R Function(T) selector) => selector(state); } /// Optimized consumer for minimal rebuilds /// /// Example: /// ```dart /// OptimizedConsumer( /// selector: (state) => state.name, /// builder: (context, name, child) { /// return Text(name); /// }, /// ) /// ``` class OptimizedConsumer extends ConsumerWidget { final ProviderListenable provider; final S Function(T) selector; final Widget Function(BuildContext context, S value, Widget? child) builder; final Widget? child; const OptimizedConsumer({ super.key, required this.provider, required this.selector, required this.builder, this.child, }); @override Widget build(BuildContext context, WidgetRef ref) { final value = ref.watch(provider.select(selector)); return builder(context, value, child); } } /// Batch state updates for performance class BatchedStateUpdates { final void Function(T) updateState; final List _pendingUpdates = []; final Debouncer _debouncer; BatchedStateUpdates( this.updateState, { int debounceDuration = 100, }) : _debouncer = Debouncer(milliseconds: debounceDuration); /// Queue an update void queueUpdate(void Function(T) update) { _pendingUpdates.add(update); _debouncer.run(_flushUpdates); } /// Flush all pending updates void _flushUpdates() { if (_pendingUpdates.isEmpty) return; // Apply all updates in batch for (final update in _pendingUpdates) { // Note: This would need proper implementation based on state type // updateState(update); } _pendingUpdates.clear(); } /// Force flush immediately void flush() { _debouncer.cancel(); _flushUpdates(); } void dispose() { _debouncer.dispose(); _pendingUpdates.clear(); } }