Line data Source code
1 : part of flutter_data;
2 :
3 : class DataState<T> with EquatableMixin {
4 : final T model;
5 : final bool isLoading;
6 : final DataException? exception;
7 : final StackTrace? stackTrace;
8 :
9 1 : const DataState(
10 : this.model, {
11 : this.isLoading = false,
12 : this.exception,
13 : this.stackTrace,
14 : });
15 :
16 2 : bool get hasException => exception != null;
17 :
18 2 : bool get hasModel => model != null;
19 :
20 1 : @override
21 4 : List<Object?> get props => [model, isLoading, exception];
22 :
23 0 : @override
24 : bool get stringify => true;
25 : }
26 :
27 : class DataException with EquatableMixin implements Exception {
28 : final Object error;
29 : final StackTrace? stackTrace;
30 : final int? statusCode;
31 :
32 1 : const DataException(this.error, {this.stackTrace, this.statusCode});
33 :
34 1 : @override
35 4 : List<Object?> get props => [error, stackTrace, statusCode];
36 :
37 1 : @override
38 : String toString() {
39 6 : return 'DataException: $error ${statusCode != null ? " [HTTP $statusCode]" : ""}\n${stackTrace ?? ''}';
40 : }
41 : }
42 :
43 : class DataStateNotifier<T> extends StateNotifier<DataState<T>> {
44 1 : DataStateNotifier({
45 : required DataState<T> data,
46 : Future<void> Function()? reload,
47 : }) : _reloadFn = reload,
48 1 : super(data);
49 :
50 : // ignore: prefer_final_fields
51 : Future<void> Function()? _reloadFn;
52 : void Function()? onDispose;
53 :
54 2 : DataState<T> get data => super.state;
55 :
56 1 : void updateWith({
57 : Object? model = stamp,
58 : bool? isLoading,
59 : Object? exception = stamp,
60 : Object? stackTrace = stamp,
61 : }) {
62 2 : super.state = DataState<T>(
63 3 : model == stamp ? state.model : model as T,
64 2 : isLoading: isLoading ?? state.isLoading,
65 : exception:
66 3 : exception == stamp ? state.exception : exception as DataException?,
67 : stackTrace:
68 3 : stackTrace == stamp ? state.stackTrace : stackTrace as StackTrace?,
69 : );
70 : }
71 :
72 1 : Future<void> reload() async {
73 2 : return _reloadFn?.call();
74 : }
75 :
76 1 : @override
77 : RemoveListener addListener(
78 : Listener<DataState<T>> listener, {
79 : bool fireImmediately = true,
80 : }) {
81 : Function? _dispose;
82 1 : if (mounted) {
83 1 : _dispose = super.addListener(listener, fireImmediately: fireImmediately);
84 : }
85 1 : return () {
86 1 : _dispose?.call();
87 2 : onDispose?.call();
88 : };
89 : }
90 :
91 1 : @override
92 : void dispose() {
93 1 : if (mounted) {
94 1 : super.dispose();
95 : }
96 : }
97 : }
98 :
99 : class _Stamp {
100 1 : const _Stamp();
101 : }
102 :
103 : const stamp = _Stamp();
104 :
105 : class _FunctionalDataStateNotifier<T, W> extends DataStateNotifier<W> {
106 : final DataStateNotifier<W> _source;
107 : late RemoveListener _sourceDisposeFn;
108 :
109 1 : _FunctionalDataStateNotifier(this._source)
110 3 : : super(data: _source.data, reload: _source._reloadFn);
111 :
112 1 : DataStateNotifier<W> where(bool Function(T) test) {
113 4 : _sourceDisposeFn = _source.addListener((state) {
114 1 : if (state.hasModel) {
115 : W _model;
116 :
117 1 : if (_typesEqual<W, List<T>?>()) {
118 3 : _model = (state.model as List<T>?)?.where(test).toList() as W;
119 1 : } else if (_typesEqual<W, T?>()) {
120 3 : _model = test(state.model as T) ? state.model : null as W;
121 : } else {
122 0 : throw UnsupportedError('W must either be T? or List<T>?');
123 : }
124 :
125 2 : super.state = DataState(_model,
126 1 : isLoading: state.isLoading,
127 1 : exception: state.exception,
128 1 : stackTrace: state.stackTrace);
129 : }
130 : });
131 : return this;
132 : }
133 :
134 1 : DataStateNotifier<W> map(T Function(T) convert) {
135 4 : _sourceDisposeFn = _source.addListener((state) {
136 1 : if (state.hasModel) {
137 : W _model;
138 :
139 1 : if (_typesEqual<W, List<T>?>()) {
140 3 : _model = (state.model as List<T>?)?.map(convert).toList() as W;
141 1 : } else if (_typesEqual<W, T>()) {
142 2 : _model = convert(state.model as T) as W;
143 : } else {
144 0 : throw UnsupportedError('W must either be T or List<T>?');
145 : }
146 :
147 2 : super.state = DataState(_model,
148 1 : isLoading: state.isLoading,
149 1 : exception: state.exception,
150 1 : stackTrace: state.stackTrace);
151 : }
152 : });
153 : return this;
154 : }
155 :
156 : late DataState<W> _bufferedState;
157 : Timer? _timer;
158 :
159 0 : DataStateNotifier<W> throttle(Duration Function() durationFn) {
160 0 : _timer = _makeTimer(durationFn);
161 0 : _sourceDisposeFn = _source.addListener((model) {
162 0 : _bufferedState = model;
163 : }, fireImmediately: false);
164 : return this;
165 : }
166 :
167 0 : Timer _makeTimer(Duration Function() durationFn) {
168 0 : return Timer(durationFn(), () {
169 0 : if (mounted) {
170 0 : super.state = _bufferedState;
171 0 : _timer = _makeTimer(durationFn); // reset timer
172 : }
173 : });
174 : }
175 :
176 2 : bool _typesEqual<T1, T2>() => T1 == T2;
177 :
178 1 : @override
179 : RemoveListener addListener(
180 : Listener<DataState<W>> listener, {
181 : bool fireImmediately = true,
182 : }) {
183 : final dispose =
184 1 : super.addListener(listener, fireImmediately: fireImmediately);
185 1 : return () {
186 1 : dispose.call();
187 1 : _timer?.cancel();
188 2 : _sourceDisposeFn.call();
189 : };
190 : }
191 :
192 0 : @override
193 : void dispose() {
194 0 : if (mounted) {
195 0 : super.dispose();
196 : }
197 0 : _source.dispose();
198 : }
199 : }
200 :
201 : /// Functional utilities for [DataStateNotifier]
202 : extension DataStateNotifierListX<T> on DataStateNotifier<List<T>?> {
203 : /// Filters all models of the list (if present) through [test]
204 1 : DataStateNotifier<List<T>?> where(bool Function(T) test) {
205 2 : return _FunctionalDataStateNotifier<T, List<T>?>(this).where(test);
206 : }
207 :
208 : /// Maps all models of the list (if present) through [convert]
209 1 : DataStateNotifier<List<T>?> map(T Function(T) convert) {
210 2 : return _FunctionalDataStateNotifier<T, List<T>?>(this).map(convert);
211 : }
212 : }
213 :
214 : /// Functional utilities for [DataStateNotifier]
215 : extension DataStateNotifierX<T> on DataStateNotifier<T> {
216 : /// Filters all models of the list (if present) through [test]
217 1 : DataStateNotifier<T> where(bool Function(T) test) {
218 2 : return _FunctionalDataStateNotifier<T, T>(this).where(test);
219 : }
220 :
221 : /// Maps all models of the list (if present) through [convert]
222 1 : DataStateNotifier<T> map(T Function(T) convert) {
223 2 : return _FunctionalDataStateNotifier<T, T>(this).map(convert);
224 : }
225 : }
|