runable
This commit is contained in:
313
.archive/provider_optimization.dart
Normal file
313
.archive/provider_optimization.dart
Normal file
@@ -0,0 +1,313 @@
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user