From 547cf65dd8a40e609ed4b221e12e9263a169a8db Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 9 Jun 2022 13:33:39 +0600 Subject: [PATCH] added lazy query support fixed possible race condition of simultanous refetches of the same query --- packages/example/lib/lazy_query.dart | 36 +++++++++++++++++++++ packages/example/lib/main.dart | 32 ++++++++++++++++-- packages/fl_query/lib/models/query_job.dart | 5 +++ packages/fl_query/lib/query.dart | 20 ++++++++++-- 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 packages/example/lib/lazy_query.dart diff --git a/packages/example/lib/lazy_query.dart b/packages/example/lib/lazy_query.dart new file mode 100644 index 0000000..a409e9c --- /dev/null +++ b/packages/example/lib/lazy_query.dart @@ -0,0 +1,36 @@ +import 'package:fl_query/fl_query.dart'; +import 'package:flutter/material.dart'; + +final lazyQueryJob = QueryJob( + queryKey: "non_enabled_query", + enabled: false, + task: (queryKey, data) { + return Future.delayed(const Duration(milliseconds: 500), + () => "Hello from $queryKey with $data"); + }); + +class LazyQuery extends StatelessWidget { + const LazyQuery({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: QueryBuilder( + job: lazyQueryJob, + externalData: "Love", + builder: (context, query) { + return Column( + children: [ + Text("Query Data::: ${query.data ?? "Loading"}"), + ElevatedButton( + child: const Text("Fetch Query"), + onPressed: () => query.refetch(), + ), + ], + ); + }, + ), + ); + } +} diff --git a/packages/example/lib/main.dart b/packages/example/lib/main.dart index 9d3aca5..25934ca 100644 --- a/packages/example/lib/main.dart +++ b/packages/example/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:example/another_component.dart'; +import 'package:example/lazy_query.dart'; import 'package:example/query_with_external_data.dart'; import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart'; @@ -35,7 +36,9 @@ final successJob = QueryJob( final failedJob = QueryJob( queryKey: "failure", - task: (queryKey, _) => Future.value("[$queryKey] Failed for unknown reason"), + task: (queryKey, _) => Random().nextBool() + ? Future.error("[$queryKey] Failed for unknown reason") + : Future.value("Success, you'll get slowly ${Random().nextInt(100)}!"), ); class MyHomePage extends StatefulWidget { @@ -90,8 +93,20 @@ class _MyHomePageState extends State { job: failedJob, externalData: null, builder: (context, query) { - if (query.hasError) return Text(query.error); - return Text("Failure. You're a failure ${query.data}"); + return Row( + children: [ + if (query.hasError) + Text( + "${query.error}. Retrying: ${query.retryAttempts}"), + if (query.hasData) + Text( + "Success after ${query.retryAttempts}. Data: ${query.data}"), + ElevatedButton( + child: Text("Refetch ${query.queryKey}"), + onPressed: () => query.refetch(), + ) + ], + ); }, ), ], @@ -106,6 +121,17 @@ class _MyHomePageState extends State { ); }, ), + const SizedBox(height: 10), + ElevatedButton( + child: const Text("Non Enabled Query Example"), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LazyQuery(), + ), + ); + }, + ), const AnotherComponent(), ], ), diff --git a/packages/fl_query/lib/models/query_job.dart b/packages/fl_query/lib/models/query_job.dart index 27ab846..a4445ae 100644 --- a/packages/fl_query/lib/models/query_job.dart +++ b/packages/fl_query/lib/models/query_job.dart @@ -8,6 +8,10 @@ class QueryJob { final Duration? retryDelay; final T? initialData; + /// If set to false then the initial fetch will not be called & to + /// start the process the user has to call the refetch first + final bool? enabled; + // got from global options final Duration? staleTime; @@ -22,5 +26,6 @@ class QueryJob { this.staleTime, this.onData, this.onError, + this.enabled, }); } diff --git a/packages/fl_query/lib/query.dart b/packages/fl_query/lib/query.dart index 32f0b19..ebf0087 100644 --- a/packages/fl_query/lib/query.dart +++ b/packages/fl_query/lib/query.dart @@ -20,6 +20,9 @@ class Query extends ChangeNotifier { // all params final String queryKey; QueryTaskFunction task; + + /// The number of times the query should refetch in the time of error + /// before giving up final int retries; final Duration retryDelay; final T? _initialData; @@ -31,9 +34,13 @@ class Query extends ChangeNotifier { T? data; dynamic error; QueryStatus status; + + /// total count of how many times the query retried to get a successful + /// result int retryAttempts = 0; DateTime updatedAt; int refetchCount = 0; + bool enabled; @protected bool fetched = false; @@ -55,6 +62,7 @@ class Query extends ChangeNotifier { required this.retries, required this.retryDelay, T? initialData, + this.enabled = true, QueryListener? onData, QueryListener? onError, }) : status = QueryStatus.pending, @@ -69,6 +77,7 @@ class Query extends ChangeNotifier { Query.fromOptions(QueryJob options, {required Outside externalData}) : queryKey = options.queryKey, + enabled = options.enabled ?? true, task = options.task, retries = options.retries ?? 3, retryDelay = options.retryDelay ?? const Duration(milliseconds: 200), @@ -90,6 +99,7 @@ class Query extends ChangeNotifier { bool get isRefetching => status == QueryStatus.refetching && (data != null || error != null); bool get isSucceeded => status == QueryStatus.succeed && data != null; + bool get isIdle => isSucceeded && error == null; Outside get externalData => _externalData; Outside? get prevUsedExternalData => _prevUsedExternalData; @@ -140,6 +150,7 @@ class Query extends ChangeNotifier { Future fetch() async { status = QueryStatus.pending; notifyListeners(); + if (!enabled) return null; if (!isStale && hasData) { return data; } @@ -149,11 +160,16 @@ class Query extends ChangeNotifier { }); } - Future refetch() { + Future refetch() async { + // cannot let run multiple refetch at the same time. It can cause + // race-condition + if (isRefetching) return null; status = QueryStatus.refetching; refetchCount++; + // disabling the lazy query bound when query was actually called + if (!enabled) enabled = false; notifyListeners(); - return _execute().then((_) => data); + return await _execute().then((_) => data); } /// can be used to update the data manually. Can be useful when used