main
1import 'package:blood_pressure_app/l10n/app_localizations.dart';
2import 'package:flutter/material.dart';
3
4/// A future builder with app defaults.
5///
6/// This allows to have the same loading style everywhere in the app.
7class ConsistentFutureBuilder<T> extends StatefulWidget {
8 /// Create a future builder with app defaults.
9 const ConsistentFutureBuilder({
10 super.key,
11 required this.future,
12 this.onNotStarted,
13 this.onWaiting,
14 required this.onData,
15 this.cacheFuture = false,
16 this.lastChildWhileWaiting = false,
17 });
18
19 /// Future that gets evaluated.
20 final Future<T> future;
21
22 /// The build strategy once the future loaded.
23 final Widget Function(BuildContext context, T result) onData;
24
25 /// The text displayed when no future is connected.
26 ///
27 /// This case should generally be avoided and is protected by assertions in
28 /// debug builds.
29 ///
30 /// Is a 'not started' text by default.
31 final Widget? onNotStarted;
32
33 /// The future loading indicator.
34 ///
35 /// Shown while the element is loading. Defaults to 'loading... text'.
36 final Widget? onWaiting;
37
38 /// Internally save the future and avoid rebuilds.
39 ///
40 /// Caching will allow the future builder not to load again in some cases
41 /// where a rebuild is triggered. But it comes at the cost that onData will
42 /// not be called again, even if data changed.
43 ///
44 /// The parameter is false by default and should only be set to true when
45 /// rebuilds are disruptive to the user and it is certain that the data will
46 /// not change over the lifetime of the screen.
47 final bool cacheFuture;
48
49 /// When loading the next result the child that got build the last time will
50 /// be returned.
51 ///
52 /// During the first build, [onWaiting] os respected instead.
53 final bool lastChildWhileWaiting;
54
55 @override
56 State<ConsistentFutureBuilder<T>> createState() =>
57 _ConsistentFutureBuilderState<T>();
58}
59
60class _ConsistentFutureBuilderState<T>
61 extends State<ConsistentFutureBuilder<T>> {
62 Future<T>? _future; // avoid rebuilds
63 /// Used for returning the last child during load when rebuilding.
64 Widget? _lastChild;
65
66 @override
67 void initState() {
68 super.initState();
69 if (widget.cacheFuture) {
70 _future = widget.future;
71 }
72 }
73
74 @override
75 Widget build(BuildContext context) => FutureBuilder<T>(
76 future: _future ?? widget.future,
77 builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
78 // Might get called before localizations initialize.
79 final localizations = AppLocalizations.of(context);
80 if (snapshot.hasError) {
81 return Directionality(
82 textDirection: TextDirection.ltr,
83 child: Text(localizations?.error(snapshot.error.toString())
84 ?? snapshot.error.toString(),),
85 );
86 }
87 switch (snapshot.connectionState) {
88 case ConnectionState.none:
89 assert(false);
90 return widget.onNotStarted ?? Text(localizations?.errNotStarted
91 ?? 'NO_LOC_NO_START: please report this error.',);
92 case ConnectionState.waiting:
93 case ConnectionState.active:
94 if (widget.lastChildWhileWaiting && _lastChild != null) {
95 return _lastChild!;
96 }
97 return widget.onWaiting ?? Text(localizations?.loading
98 ?? 'loading...',);
99 case ConnectionState.done:
100 _lastChild = widget.onData(context, snapshot.data!);
101 return _lastChild!;
102 }
103 },
104 );
105}