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