Commit 60fd806

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-10-08 11:27:55
fix consistent_future_builder.dart caching too much
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 02692da
lib/components/consistent_future_builder.dart
@@ -3,35 +3,52 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
 class ConsistentFutureBuilder<T> extends StatefulWidget {
   /// Future that gets evaluated.
-  ///
-  /// This Future is saved as a state, so the build function won't get called again when a rebuild occurs. The future
-  /// gets evaluated once.
   final Future<T> future;
   final Widget Function(BuildContext context, T result) onData;
   
   final Widget? onNotStarted;
   final Widget? onWaiting;
   final Widget? Function(BuildContext context, String errorMsg)? onError;
-  
-  const ConsistentFutureBuilder({super.key, required this.future, this.onNotStarted, this.onWaiting, this.onError, required this.onData});
+
+  /// Internally save the future and avoid rebuilds.
+  ///
+  /// Caching will allow the future builder not to load again in some cases where a rebuild is triggered. But it comes at
+  /// the cost that onData will not be called again, even if data changed.
+  ///
+  /// The parameter is false by default and should only be set to true when rebuilds are disruptive to the user and it
+  /// is certain that the data will not change over the lifetime of the screen.
+  final bool cacheFuture;
+
+  /// When loading the next result the child that got build the last time will be returned.
+  ///
+  /// If this is the first build, [onWaiting] os respected.
+  final bool lastChildWhileWaiting;
+
+  const ConsistentFutureBuilder({super.key, required this.future, this.onNotStarted, this.onWaiting, this.onError,
+    required this.onData, this.cacheFuture = false, this.lastChildWhileWaiting = false});
 
   @override
   State<ConsistentFutureBuilder<T>> createState() => _ConsistentFutureBuilderState<T>();
 }
 
 class _ConsistentFutureBuilderState<T> extends State<ConsistentFutureBuilder<T>> {
-  late final Future<T> _future; // avoid rebuilds
+  Future<T>? _future; // avoid rebuilds
+  /// Used for returning the last child during load when rebuilding.
+  Widget? _lastChild;
 
   @override
   void initState() {
     super.initState();
-    _future = widget.future;
+    if (widget.cacheFuture) {
+      _future = widget.future;
+    }
+     // TODO: avoid functionality that avoids rebuilds, as this causes various issues with widgets that should be rebuild like the measurement list
   }
 
   @override
   Widget build(BuildContext context) {
     return FutureBuilder<T>(
-      future: _future,
+      future: _future ?? widget.future,
       builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
         if (snapshot.hasError) {
           return Text(AppLocalizations.of(context)!.error(snapshot.error.toString()));
@@ -41,9 +58,11 @@ class _ConsistentFutureBuilderState<T> extends State<ConsistentFutureBuilder<T>>
             return widget.onNotStarted ?? Text(AppLocalizations.of(context)!.errNotStarted);
           case ConnectionState.waiting:
           case ConnectionState.active:
+            if (widget.lastChildWhileWaiting && _lastChild != null) return _lastChild!;
             return widget.onWaiting ?? Text(AppLocalizations.of(context)!.loading);
           case ConnectionState.done:
-            return widget.onData(context, snapshot.data as T);
+            _lastChild = widget.onData(context, snapshot.data as T);
+            return _lastChild!;
         }
       });
   }
lib/components/export_item_order.dart
@@ -29,6 +29,7 @@ class _ExportItemsCustomizerState extends State<ExportItemsCustomizer> {
   Widget build(BuildContext context) {
     return ConsistentFutureBuilder(
       future: ExportConfigurationModel.get(AppLocalizations.of(context)!),
+      cacheFuture: true,
       onData: (BuildContext context, ExportConfigurationModel result) {
         return _buildAddItemBadge(context, result,
           child: _buildManagePresetsBadge(context, result,
lib/model/export_options.dart
@@ -15,7 +15,7 @@ class ExportFields {
   static const defaultPdf = ['formattedTimestamp','systolic','diastolic','pulse','notes']; 
 }
 
-class ExportConfigurationModel {
+class ExportConfigurationModel extends ChangeNotifier {
   // 2 sources.
   static ExportConfigurationModel? _instance;
 
@@ -89,6 +89,7 @@ class ExportConfigurationModel {
     _availableFormats.removeWhere((e) => e.internalName == format.internalName);
     _availableFormats.add(format);
     _configDao.updateExportColumn(format);
+    notifyListeners();
   }
 
   void delete(ExportColumn format) {
@@ -96,6 +97,7 @@ class ExportConfigurationModel {
     assert(existingEntries.isNotEmpty, r"Tried to delete entry that doesn't exist or is not editable.");
     _availableFormats.removeWhere((element) => element.internalName == format.internalName);
     _configDao.deleteExportColumn(format.internalName);
+    notifyListeners();
   }
 
   UnmodifiableListView<ExportColumn> get availableFormats => UnmodifiableListView(_availableFormats);
lib/screens/subsettings/export_import_screen.dart
@@ -206,6 +206,7 @@ class ExportFieldCustomisationSetting extends StatelessWidget {
     final localizations = AppLocalizations.of(context)!;
     return ConsistentFutureBuilder(
       future: ExportConfigurationModel.get(localizations),
+      lastChildWhileWaiting: true,
       onData: (context, configurationModel) {
         return Consumer<ExportSettings>(builder: (context, settings, child) {
           final formats = configurationModel.availableFormats.toSet();
@@ -306,6 +307,7 @@ class _ExportWarnBannerState extends State<ExportWarnBanner> {
           Consumer<PdfExportSettings>(builder: (context, pdfExportSettings, child) =>
             ConsistentFutureBuilder(
               future: ExportConfigurationModel.get(localizations),
+              lastChildWhileWaiting: true,
               onData: (context, configurationModel) {
                 String? message;
                 final CustomFieldsSettings fieldSettings = (exportSettings.exportFormat == ExportFormat.csv
@@ -339,8 +341,7 @@ class _ExportWarnBannerState extends State<ExportWarnBanner> {
                 return const SizedBox.shrink();
               }))
         )
-      )
-    );
+    ));
   }
 }
 
lib/screens/settings.dart
@@ -359,6 +359,7 @@ class SettingsPage extends StatelessWidget {
                   trailing: const Icon(Icons.arrow_forward_ios),
                   description: ConsistentFutureBuilder<PackageInfo>(
                     future: PackageInfo.fromPlatform(),
+                    cacheFuture: true,
                     onData: (context, info) => Text(info.version)
                   ),
                   onPressed: (context) {
lib/main.dart
@@ -46,6 +46,7 @@ void main() async {
     ChangeNotifierProvider(create: (context) => csvExportSettings),
     ChangeNotifierProvider(create: (context) => pdfExportSettings),
     ChangeNotifierProvider(create: (context) => intervalStorageManager),
+    ChangeNotifierProvider(create: (context) => intervalStorageManager),
   ], child: const AppRoot()));
 }