Skip to content

Commit 9343fb4

Browse files
author
Jens Becker
committed
[Add] EasyStreamBuilder & EasyFutureBuilder Widgets
1 parent bc163af commit 9343fb4

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

lib/flutter_extensions.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export 'src/date_time_extensions.dart';
55
export 'src/list_extensions.dart';
66
export 'src/list_extensions.dart';
77
export 'src/string_extensions.dart';
8+
export 'src/widgets.dart';

lib/src/widgets.dart

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
4+
5+
import 'adaptive_helpers.dart';
6+
7+
/// This widget makes it easy to display the various states of getting data via a [Stream].
8+
///
9+
/// Currently the snapshot data has to be of type iterable or map.
10+
class EasyStreamBuilder<T> extends StatelessWidget {
11+
const EasyStreamBuilder({
12+
required this.stream,
13+
required this.errorText,
14+
required this.noDataText,
15+
this.noDataIcon,
16+
required this.builder,
17+
this.loadingIndicator = const CircularProgressIndicator(),
18+
this.color = Colors.black,
19+
});
20+
21+
final Stream<T> stream;
22+
final String errorText;
23+
final String noDataText;
24+
final IconData? noDataIcon;
25+
final Widget Function(BuildContext context, T data) builder;
26+
final Widget loadingIndicator;
27+
final Color color;
28+
29+
@override
30+
Widget build(BuildContext context) {
31+
return StreamBuilder(
32+
stream: stream,
33+
builder: (context, snapshot) {
34+
if (snapshot.hasError) {
35+
return SnapshotHasError(
36+
text: errorText,
37+
errorMessage: snapshot.error.toString(),
38+
color: color,
39+
);
40+
} else if (!snapshot.hasData) {
41+
return snapshot.connectionState == ConnectionState.waiting
42+
? loadingIndicator
43+
: SnapshotHasError(
44+
text: errorText,
45+
errorMessage: snapshot.error?.toString() ?? '',
46+
color: color,
47+
);
48+
} else {
49+
assert(
50+
snapshot.data is Iterable || snapshot.data is Map,
51+
'The stream must be of type Iterable or Map!',
52+
);
53+
54+
try {
55+
late final bool noData;
56+
57+
if (snapshot.data is Iterable) {
58+
noData = (snapshot.data as Iterable).isEmpty;
59+
} else if (snapshot.data is Map) {
60+
noData = (snapshot.data as Map).isEmpty;
61+
}
62+
63+
return noData
64+
? SnapshotHasNoData(
65+
text: noDataText,
66+
iconData: noDataIcon,
67+
color: color,
68+
)
69+
: builder(context, snapshot.data as T);
70+
} catch (e) {
71+
return SnapshotHasError(
72+
text: errorText,
73+
errorMessage: e.toString(),
74+
color: color,
75+
);
76+
}
77+
}
78+
},
79+
);
80+
}
81+
}
82+
83+
/// This widget makes it easy to display the various states of getting data via a [Future].
84+
///
85+
/// Currently the snapshot data has to be of type iterable or map.
86+
class EasyFutureBuilder<T> extends StatelessWidget {
87+
const EasyFutureBuilder({
88+
required this.future,
89+
required this.errorText,
90+
required this.noDataText,
91+
required this.noDataIcon,
92+
required this.builder,
93+
this.loadingIndicator = const CircularProgressIndicator(),
94+
this.color = Colors.black,
95+
});
96+
97+
final Future<T> future;
98+
final String errorText;
99+
final String noDataText;
100+
final IconData noDataIcon;
101+
final Widget Function(BuildContext context, T data) builder;
102+
final Widget loadingIndicator;
103+
final Color color;
104+
105+
@override
106+
Widget build(BuildContext context) {
107+
return FutureBuilder(
108+
future: future,
109+
builder: (context, snapshot) {
110+
if (snapshot.hasError) {
111+
return SnapshotHasError(
112+
text: errorText,
113+
errorMessage: snapshot.error.toString(),
114+
color: color,
115+
);
116+
} else if (!snapshot.hasData) {
117+
return snapshot.connectionState == ConnectionState.waiting
118+
? loadingIndicator
119+
: SnapshotHasError(
120+
text: errorText,
121+
errorMessage: snapshot.error?.toString() ?? '',
122+
color: color,
123+
);
124+
} else {
125+
assert(
126+
snapshot.data is Iterable || snapshot.data is Map,
127+
'The future must be of type Iterable or Map!',
128+
);
129+
130+
try {
131+
late final bool noData;
132+
133+
if (snapshot.data is Iterable) {
134+
noData = (snapshot.data as Iterable).isEmpty;
135+
} else if (snapshot.data is Map) {
136+
noData = (snapshot.data as Map).isEmpty;
137+
}
138+
139+
return noData
140+
? SnapshotHasNoData(
141+
text: noDataText,
142+
iconData: noDataIcon,
143+
color: color,
144+
)
145+
: builder(context, snapshot.data as T);
146+
} catch (e) {
147+
return SnapshotHasError(
148+
text: errorText,
149+
errorMessage: e.toString(),
150+
color: color,
151+
);
152+
}
153+
}
154+
},
155+
);
156+
}
157+
}
158+
159+
/// This widget can be used to show that there is an error.
160+
class SnapshotHasError extends StatelessWidget {
161+
const SnapshotHasError({
162+
required this.text,
163+
required this.errorMessage,
164+
this.color = Colors.black,
165+
});
166+
167+
final String text;
168+
final String errorMessage;
169+
final Color color;
170+
171+
@override
172+
Widget build(BuildContext context) {
173+
return Center(
174+
child: Padding(
175+
padding: const EdgeInsets.all(Insets.m),
176+
child: Column(
177+
mainAxisAlignment: MainAxisAlignment.center,
178+
crossAxisAlignment: CrossAxisAlignment.center,
179+
children: [
180+
FaIcon(
181+
FontAwesomeIcons.exclamationCircle,
182+
size: 55,
183+
color: color,
184+
),
185+
const SizedBox(height: Insets.m),
186+
SelectableText(
187+
text,
188+
style:
189+
Theme.of(context).textTheme.bodyText1!.copyWith(color: color),
190+
textAlign: TextAlign.center,
191+
),
192+
if (kDebugMode)
193+
Padding(
194+
padding: const EdgeInsets.all(Insets.m),
195+
child: SelectableText(
196+
errorMessage,
197+
textAlign: TextAlign.center,
198+
style: TextStyle(color: color),
199+
),
200+
),
201+
],
202+
),
203+
),
204+
);
205+
}
206+
}
207+
208+
/// This widget can be used to show that there is no data.
209+
class SnapshotHasNoData extends StatelessWidget {
210+
const SnapshotHasNoData({
211+
required this.text,
212+
this.iconData,
213+
this.color = Colors.black,
214+
});
215+
216+
final String text;
217+
final IconData? iconData;
218+
final Color color;
219+
220+
@override
221+
Widget build(BuildContext context) {
222+
return Padding(
223+
padding: const EdgeInsets.all(Insets.m),
224+
child: Center(
225+
child: Column(
226+
mainAxisAlignment: MainAxisAlignment.center,
227+
children: [
228+
if (iconData != null) ...[
229+
Padding(
230+
padding: const EdgeInsets.all(Insets.xxl),
231+
child: FaIcon(
232+
iconData,
233+
size: 50,
234+
color: color,
235+
),
236+
),
237+
],
238+
SelectableText(
239+
text,
240+
style: TextStyle(color: color),
241+
textAlign: TextAlign.center,
242+
),
243+
],
244+
),
245+
),
246+
);
247+
}
248+
}

0 commit comments

Comments
 (0)