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