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