Files
retail/.archive/provider_optimization.dart
Phuoc Nguyen b94c158004 runable
2025-10-10 16:38:07 +07:00

314 lines
7.4 KiB
Dart

/// 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<S, T>(ProviderListenable<S> 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<S, T>(ProviderListenable<S> 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<T>(
ProviderListenable<T> 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<T>(
provider,
(prev, next) {
if (condition(prev, next)) {
listener(prev, next);
previous = next;
}
},
onError: onError,
);
}
}
/// Debounced state notifier for performance optimization
abstract class DebouncedStateNotifier<T> extends StateNotifier<T> {
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<T> {
final AsyncValue<T> _value;
final DateTime _timestamp;
final Duration _cacheDuration;
CachedAsyncValue(
this._value, {
Duration cacheDuration = const Duration(minutes: 5),
}) : _timestamp = DateTime.now(),
_cacheDuration = cacheDuration;
AsyncValue<T> get value => _value;
bool get isExpired {
return DateTime.now().difference(_timestamp) > _cacheDuration;
}
bool get isValid {
return !isExpired && _value is AsyncData<T>;
}
}
/// Provider cache manager
class ProviderCacheManager {
static final Map<String, CachedAsyncValue> _cache = {};
/// Get cached value or compute new one
static AsyncValue<T> getOrCompute<T>({
required String key,
required AsyncValue<T> Function() compute,
Duration cacheDuration = const Duration(minutes: 5),
}) {
final cached = _cache[key] as CachedAsyncValue<T>?;
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<Arg, T> {
final Map<Arg, T> _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<T> on StateNotifier<T> {
/// 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>(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<T> {
final T state;
const StateSelector(this.state);
/// Select a field from state
S select<S>(S Function(T) selector) => selector(state);
/// Select multiple fields
R selectAll<R>(R Function(T) selector) => selector(state);
}
/// Optimized consumer for minimal rebuilds
///
/// Example:
/// ```dart
/// OptimizedConsumer<UserState>(
/// selector: (state) => state.name,
/// builder: (context, name, child) {
/// return Text(name);
/// },
/// )
/// ```
class OptimizedConsumer<T, S> extends ConsumerWidget {
final ProviderListenable<T> 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<T> {
final void Function(T) updateState;
final List<void Function(T)> _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();
}
}