import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; /// Same as [Consumer] but only notifies of a new value class Listener extends SingleChildStatefulWidget { const Listener({required this.listener, super.key, super.child}); final void Function(BuildContext context, T? previousValue, T currentValue) listener; @override State> createState() => _ListenerState(); } class _ListenerState extends SingleChildState> { T? _oldValue; @override Widget buildWithChild(BuildContext context, Widget? child) { final T? oldValue = _oldValue; final T newValue = context.watch(); _oldValue = newValue; widget.listener(context, oldValue, newValue); return child ?? const SizedBox.shrink(); } } class ChangeNotifierListener extends StatefulWidget { const ChangeNotifierListener({ required this.child, required this.listener, super.key, }); final Widget child; final Function(BuildContext context, T notifier) listener; @override State> createState() => _ChangeNotifierListenerState(); } class _ChangeNotifierListenerState extends State> { T? _notifier; @override void didChangeDependencies() { super.didChangeDependencies(); _notifier ??= context.read()..replaceListener(_onNotifierChanged); } @override Widget build(BuildContext context) { return widget.child; } void _onNotifierChanged() { widget.listener(context, _notifier!); } @override void dispose() { _notifier?.removeListener(_onNotifierChanged); _notifier = null; super.dispose(); } } /// Same as [Listener] but for [ValueNotifier] : notifies when the value changes class ValueNotifierListener, S> extends SingleChildStatefulWidget { const ValueNotifierListener({ this.listener, this.listenerWithValueNotifier, super.key, super.child, }) : assert( listener != null || listenerWithValueNotifier != null, 'At least one listener must be provided', ); final void Function(BuildContext context, S? previousValue, S currentValue)? listener; final void Function( BuildContext context, T valueNotifier, S? previousValue, S currentValue, )? listenerWithValueNotifier; @override State> createState() => _ValueNotifierListenerState(); } class _ValueNotifierListenerState, S> extends SingleChildState> { S? _oldValue; @override Widget buildWithChild(BuildContext context, Widget? child) { final S? oldValue = _oldValue; final T valueNotifier = context.watch(); final S newValue = valueNotifier.value; _oldValue = newValue; widget.listener?.call(context, oldValue, newValue); widget.listenerWithValueNotifier?.call( context, valueNotifier, oldValue, newValue, ); return child ?? const SizedBox.shrink(); } } /// Same as [Consumer] but only rebuilds if [buildWhen] returns true /// (And on the first build) class ConsumerFilter extends StatefulWidget { const ConsumerFilter({ required this.builder, required this.buildWhen, this.child, super.key, }); final Widget Function(BuildContext context, T value, Widget? child) builder; final bool Function(T? previousValue, T currentValue) buildWhen; final Widget? child; @override State> createState() => _ConsumerFilterState(); } class _ConsumerFilterState extends State> { T? oldValue; Widget? oldWidget; @override Widget build(BuildContext context) { return Consumer( builder: (BuildContext context, T value, Widget? child) { if (widget.buildWhen(oldValue, value) || oldWidget == null) { oldWidget = widget.builder(context, value, child); } oldValue = value; return widget.builder(context, value, oldWidget); }, child: widget.child, ); } } /// Same as [Consumer] for [ValueNotifier] but only rebuilds if [buildWhen] /// returns true (and on the first build). class ConsumerValueNotifierFilter, S> extends StatefulWidget { const ConsumerValueNotifierFilter({ required this.builder, this.buildWhen, this.child, super.key, }); final Widget Function(BuildContext context, S value, Widget? child) builder; final bool Function(S? previousValue, S currentValue)? buildWhen; final Widget? child; @override State> createState() => _ConsumerValueNotifierFilterState(); } class _ConsumerValueNotifierFilterState, S> extends State> { S? oldValue; Widget? oldWidget; @override Widget build(BuildContext context) { return Consumer( builder: (BuildContext context, T provider, Widget? child) { if ((widget.buildWhen != null && widget.buildWhen!.call(oldValue, provider.value)) || widget.buildWhen == null && oldValue != provider.value || oldWidget == null) { oldWidget = widget.builder(context, provider.value, child); } oldValue = provider.value; return widget.builder(context, provider.value, oldWidget); }, child: widget.child, ); } } extension ChangeNotifierExtensions on ChangeNotifier { void replaceListener(VoidCallback listener) { removeListener(listener); addListener(listener); } } extension ValueNotifierExtensions on ValueNotifier { void emit(T value) => this.value = value; } extension ProviderExtension on BuildContext { T? readSafe() { try { return read(); } catch (_) { return null; } } T? watchSafe() { try { return watch(); } catch (_) { return null; } } }