Line data Source code
1 : library notifier_extension; 2 : 3 : import 'dart:async'; 4 : 5 : import 'package:state_notifier/state_notifier.dart'; 6 : 7 : class DelayedStateNotifier<T> extends StateNotifier<T?> { 8 22 : DelayedStateNotifier() : super(null); 9 : 10 11 : @override 11 11 : set state(T? value) => super.state = value; 12 : 13 5 : @override 14 : RemoveListener addListener(void Function(T) listener, 15 : {bool fireImmediately = true}) { 16 5 : outerListener(T? value) { 17 : // if `value` is `null` and `T` is actually a nullable 18 : // type, then the listener MUST be called with `null` 19 5 : if (_typesEqual<T, T?>() && value == null) { 20 1 : listener(null as T); 21 : } else if (value != null) { 22 : // if `value != null` and `T` is non-nullable, also 23 5 : listener(value); 24 : } 25 : } 26 : 27 5 : return super.addListener(outerListener, fireImmediately: false); 28 : } 29 : 30 : Function? onDispose; 31 : 32 10 : @override 33 : void dispose() { 34 10 : super.dispose(); 35 10 : onDispose?.call(); 36 : } 37 : 38 10 : bool _typesEqual<T1, T2>() => T1 == T2; 39 : } 40 : 41 : class _FunctionalStateNotifier<S, T> extends DelayedStateNotifier<T> { 42 : final DelayedStateNotifier<S> _source; 43 : final String? name; 44 : late RemoveListener _sourceDisposeFn; 45 : Timer? _timer; 46 : 47 5 : _FunctionalStateNotifier(this._source, {this.name}); 48 : 49 2 : DelayedStateNotifier<T> where(bool Function(S) test) { 50 8 : _sourceDisposeFn = _source.addListener((newState) { 51 2 : if (test(newState)) { 52 2 : state = newState as T; 53 : } 54 : }, fireImmediately: false); 55 : return this; 56 : } 57 : 58 2 : DelayedStateNotifier<T> map(T Function(S) convert) { 59 8 : _sourceDisposeFn = _source.addListener((state) { 60 4 : super.state = convert(state); 61 : }, fireImmediately: false); 62 : return this; 63 : } 64 : 65 : final _bufferedState = <S>[]; 66 : 67 3 : DelayedStateNotifier<T> throttle(Duration Function() durationFn) { 68 6 : _timer = _makeTimer(durationFn); 69 12 : _sourceDisposeFn = _source.addListener((model) { 70 6 : _bufferedState.add(model); 71 : }, fireImmediately: false); 72 : return this; 73 : } 74 : 75 3 : Timer _makeTimer(Duration Function() durationFn) { 76 9 : return Timer(durationFn(), () { 77 3 : if (mounted) { 78 6 : if (_bufferedState.isNotEmpty) { 79 : // Cloning the bufferedState list to force 80 : // calling listeners as workaround (need to figure out 81 : // where they are previously updated and why 82 : // super.state == _bufferedState -- and thus no update) 83 9 : super.state = [..._bufferedState] as T; // since T == List<S>; 84 6 : _bufferedState.clear(); // clear buffer 85 : } 86 6 : _timer = _makeTimer(durationFn); // reset timer 87 : } 88 : }); 89 : } 90 : 91 5 : @override 92 : RemoveListener addListener( 93 : Listener<T> listener, { 94 : bool fireImmediately = true, 95 : }) { 96 : final dispose = 97 5 : super.addListener(listener, fireImmediately: fireImmediately); 98 5 : return () { 99 5 : dispose.call(); 100 8 : _timer?.cancel(); 101 10 : _sourceDisposeFn.call(); 102 : }; 103 : } 104 : 105 0 : @override 106 : void dispose() { 107 0 : if (mounted) { 108 0 : super.dispose(); 109 : } 110 0 : _source.dispose(); 111 0 : _timer?.cancel(); 112 : } 113 : } 114 : 115 : /// Functional utilities for [StateNotifier] 116 : extension StateNotifierX<T> on DelayedStateNotifier<T> { 117 : /// Filters incoming events by [test] 118 2 : DelayedStateNotifier<T> where(bool Function(T) test) { 119 4 : return _FunctionalStateNotifier<T, T>(this, name: 'where').where(test); 120 : } 121 : 122 : /// Maps events of type [T] onto events of type [R] via [convert] 123 2 : DelayedStateNotifier<R> map<R>(R Function(T) convert) { 124 4 : return _FunctionalStateNotifier<T, R>(this, name: 'map').map(convert); 125 : } 126 : 127 : /// Buffers all incoming [T] events for a duration obtained via 128 : /// [durationFn] and emits them as a [List<T>] (unless there were none) 129 3 : DelayedStateNotifier<List<T>> throttle(Duration Function() durationFn) { 130 3 : return _FunctionalStateNotifier<T, List<T>>(this, name: 'throttle') 131 3 : .throttle(durationFn); 132 : } 133 : }