diff --git a/packages/fl_query/example/.flutter-plugins-dependencies b/packages/fl_query/example/.flutter-plugins-dependencies index bd94bf0..f66d911 100644 --- a/packages/fl_query/example/.flutter-plugins-dependencies +++ b/packages/fl_query/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]}],"date_created":"2022-10-07 11:18:58.310019","version":"3.3.4"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/opt/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]}],"date_created":"2022-10-08 11:24:03.169440","version":"3.3.4"} \ No newline at end of file diff --git a/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so b/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so index 6513d0f..c0ea40c 100644 Binary files a/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so and b/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so differ diff --git a/packages/fl_query/lib/src/base_query.dart b/packages/fl_query/lib/src/base_query.dart index 004421d..0674a34 100644 --- a/packages/fl_query/lib/src/base_query.dart +++ b/packages/fl_query/lib/src/base_query.dart @@ -41,6 +41,9 @@ abstract class BaseQuery Timer? _refetchIntervalTimer; + bool? refetchOnApplicationResume; + bool? refetchOnWindowFocus; + Connectivity _connectivity; T? _previousData; @@ -56,6 +59,8 @@ abstract class BaseQuery this.refetchOnMount, this.refetchOnReconnect, this.refetchInterval, + this.refetchOnApplicationResume, + this.refetchOnWindowFocus, this.enabled = true, T? previousData, Connectivity? connectivity, @@ -245,6 +250,8 @@ abstract class BaseQuery Duration? cacheTime, bool? refetchOnMount, bool? refetchOnReconnect, + bool? refetchOnApplicationResume, + bool? refetchOnWindowFocus, }) { bool updated = false; if (this.refetchInterval == null && @@ -272,6 +279,15 @@ abstract class BaseQuery this.refetchOnReconnect = refetchOnReconnect; updated = true; } + if (this.refetchOnApplicationResume == null && + refetchOnApplicationResume != null) { + this.refetchOnApplicationResume = refetchOnApplicationResume; + updated = true; + } + if (this.refetchOnWindowFocus == null && refetchOnWindowFocus != null) { + this.refetchOnWindowFocus = refetchOnWindowFocus; + updated = true; + } if (updated) notifyListeners(); } diff --git a/packages/fl_query/lib/src/infinite_query.dart b/packages/fl_query/lib/src/infinite_query.dart index 1f3359f..9a702f3 100644 --- a/packages/fl_query/lib/src/infinite_query.dart +++ b/packages/fl_query/lib/src/infinite_query.dart @@ -62,6 +62,7 @@ class InfiniteQuery super.enabled, super.previousData, super.connectivity, + super.refetchOnApplicationResume, InfiniteQueryListeners? super.onData, InfiniteQueryListeners? super.onError, required T? initialPage, @@ -93,6 +94,9 @@ class InfiniteQuery connectivity: options.connectivity ?? Connectivity(), queryKey: options.queryKey, initialData: {options.initialParam: options.initialPage}, + refetchOnApplicationResume: options.refetchOnApplicationResume, + onData: onData, + onError: onError, ); List get pageParams => data?.keys.toList() ?? []; diff --git a/packages/fl_query/lib/src/models/infinite_query_job.dart b/packages/fl_query/lib/src/models/infinite_query_job.dart index cc3d7ae..d92b66b 100644 --- a/packages/fl_query/lib/src/models/infinite_query_job.dart +++ b/packages/fl_query/lib/src/models/infinite_query_job.dart @@ -19,6 +19,8 @@ class InfiniteQueryJob { final bool? refetchOnMount; final bool? refetchOnReconnect; final bool? refetchOnExternalDataChange; + final bool? refetchOnApplicationResume; + final bool? refetchOnWindowFocus; final Duration? staleTime; final Duration? cacheTime; @@ -46,6 +48,8 @@ class InfiniteQueryJob { this.refetchOnMount, this.refetchOnReconnect, this.refetchOnExternalDataChange, + this.refetchOnApplicationResume, + this.refetchOnWindowFocus, this.connectivity, }) : _queryKey = queryKey; @@ -72,6 +76,8 @@ class InfiniteQueryJob { bool? refetchOnMount, bool? refetchOnReconnect, bool? refetchOnExternalDataChange, + bool? refetchOnApplicationResume, + bool? refetchOnWindowFocus, Connectivity? connectivity, }) { return (String queryKey) { @@ -91,6 +97,8 @@ class InfiniteQueryJob { refetchOnMount: refetchOnMount, refetchOnReconnect: refetchOnReconnect, refetchOnExternalDataChange: refetchOnExternalDataChange, + refetchOnApplicationResume: refetchOnApplicationResume, + refetchOnWindowFocus: refetchOnWindowFocus, connectivity: connectivity, initialParam: initialParam, ); diff --git a/packages/fl_query/lib/src/models/query_job.dart b/packages/fl_query/lib/src/models/query_job.dart index e0879f6..4886021 100644 --- a/packages/fl_query/lib/src/models/query_job.dart +++ b/packages/fl_query/lib/src/models/query_job.dart @@ -18,6 +18,8 @@ class QueryJob { final bool? refetchOnMount; final bool? refetchOnReconnect; final bool? refetchOnExternalDataChange; + final bool? refetchOnApplicationResume; + final bool? refetchOnWindowFocus; final bool? keepPreviousData; final Duration? staleTime; final Duration? cacheTime; @@ -43,6 +45,8 @@ class QueryJob { this.refetchOnExternalDataChange, this.connectivity, this.keepPreviousData, + this.refetchOnApplicationResume, + this.refetchOnWindowFocus, }) : _queryKey = queryKey; String get queryKey => _queryKey; @@ -65,6 +69,8 @@ class QueryJob { bool? refetchOnMount, bool? refetchOnReconnect, bool? refetchOnExternalDataChange, + bool? refetchOnApplicationResume, + bool? refetchOnWindowFocus, Connectivity? connectivity, bool? keepPreviousData, }) { @@ -85,6 +91,8 @@ class QueryJob { refetchOnExternalDataChange: refetchOnExternalDataChange, connectivity: connectivity, keepPreviousData: keepPreviousData, + refetchOnApplicationResume: refetchOnApplicationResume, + refetchOnWindowFocus: refetchOnWindowFocus, ); query.isDynamic = true; return query; diff --git a/packages/fl_query/lib/src/query.dart b/packages/fl_query/lib/src/query.dart index 8fb6722..5b2a653 100644 --- a/packages/fl_query/lib/src/query.dart +++ b/packages/fl_query/lib/src/query.dart @@ -58,6 +58,7 @@ class Query extends BaseQuery { super.previousData, super.connectivity, super.initialData, + super.refetchOnApplicationResume, QueryListener? super.onData, QueryListener? super.onError, }); @@ -84,6 +85,9 @@ class Query extends BaseQuery { connectivity: options.connectivity ?? Connectivity(), previousData: previousData, queryKey: options.queryKey, + refetchOnApplicationResume: options.refetchOnApplicationResume, + onData: onData, + onError: onError, ); @override diff --git a/packages/fl_query/lib/src/query_bowl.dart b/packages/fl_query/lib/src/query_bowl.dart index e84cdf8..08742c9 100644 --- a/packages/fl_query/lib/src/query_bowl.dart +++ b/packages/fl_query/lib/src/query_bowl.dart @@ -4,6 +4,7 @@ import 'package:fl_query/src/query_cache.dart'; import 'package:fl_query/src/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/services.dart'; /// The widget that holds every [Query] & [Mutation] to your /// entire Flutter application in anywhere @@ -35,13 +36,31 @@ class QueryBowl { /// refetch query when new query instance mounts final bool refetchOnMount; - /// for desktop & web only (DUMMY & isn't implemented yet) + /// refetch when desktop/web app regains Focus final bool refetchOnWindowFocus; - /// for mobile only (DUMMY & isn't implemented yet) + /// the delay to call each query when desktop/web application gets focused after + /// being unfocused. Using a delay after each refetch so refetching all the + /// queries at once won't create high CPU spikes & also wouldn't violate + /// rate-limit + /// + /// Though its recommended most of the time to use but it can be turned + /// off by passing [Duration.zero] + final Duration refetchOnWindowFocusDelay; + + /// refetch when application resumes from the background in mobile devices final bool refetchOnApplicationResume; - /// refetch when user's device reconnects to the internet after no being + /// the delay to call each query when app resumes from the background in + /// mobile devices. Using a delay after each refetch so refetching all the + /// queries at once won't create high CPU spikes & also wouldn't violate + /// rate-limit + /// + /// Though its recommended most of the time to use but it can be turned + /// off by passing [Duration.zero] + final Duration refetchOnApplicationResumeDelay; + + /// refetch when user's device reconnects to the internet after not being /// connected before final bool refetchOnReconnect; @@ -82,6 +101,8 @@ class QueryBowl { this.refetchOnMount = false, this.refetchOnReconnect = true, this.refetchOnReconnectDelay = const Duration(milliseconds: 100), + this.refetchOnApplicationResumeDelay = const Duration(milliseconds: 100), + this.refetchOnWindowFocusDelay = const Duration(milliseconds: 100), this.refetchOnApplicationResume = true, this.refetchOnWindowFocus = true, this.refetchOnExternalDataChange = false, @@ -103,8 +124,33 @@ class QueryBowl { } } }); + + if (kIsMobile) { + SystemChannels.lifecycle.setMessageHandler((msg) async { + if (msg == 'AppLifecycleState.resumed') { + if (_canNotRefetchAfterWeGotTheApp) return null; + for (final query in this.cache.queries) { + if (query.refetchOnApplicationResume == false || !query.enabled) + continue; + await query.refetch(); + await Future.delayed(refetchOnApplicationResumeDelay); + } + for (final infiniteQuery in this.cache.infiniteQueries) { + if (infiniteQuery.refetchOnApplicationResume == false || + !infiniteQuery.enabled) continue; + await infiniteQuery.refetchPages(); + await Future.delayed(refetchOnApplicationResumeDelay); + } + } else if (msg != null) { + updateWeLostTheApp(); + } + return null; + }); + } } + DateTime? _weLostTheAppAt; + /// Returns the number of query is currently fetching or refetching int get isFetching { return cache.queries.fold( @@ -127,6 +173,30 @@ class QueryBowl { ); } + bool get _canNotRefetchAfterWeGotTheApp => (_weLostTheAppAt != null && + _weLostTheAppAt!.difference(DateTime.now()) <= cache.cacheTime); + + @protected + updateWeLostTheApp() { + _weLostTheAppAt = DateTime.now(); + } + + @protected + notifyWindowFocused() async { + if (kIsMobile || _canNotRefetchAfterWeGotTheApp) return; + for (final query in this.cache.queries) { + if (query.refetchOnWindowFocus == false || !query.enabled) continue; + await query.refetch(); + await Future.delayed(refetchOnWindowFocusDelay); + } + for (final infiniteQuery in this.cache.infiniteQueries) { + if (infiniteQuery.refetchOnWindowFocus == false || !infiniteQuery.enabled) + continue; + await infiniteQuery.refetchPages(); + await Future.delayed(refetchOnWindowFocusDelay); + } + } + void onQueriesUpdate( void Function(Query query) listener, ) { @@ -271,6 +341,8 @@ class QueryBowl { refetchInterval: refetchInterval, refetchOnMount: refetchOnMount, refetchOnReconnect: refetchOnReconnect, + refetchOnApplicationResume: refetchOnApplicationResume, + refetchOnWindowFocus: refetchOnWindowFocus, ); return query; } @@ -290,6 +362,8 @@ class QueryBowl { refetchInterval: refetchInterval, refetchOnMount: refetchOnMount, refetchOnReconnect: refetchOnReconnect, + refetchOnApplicationResume: refetchOnApplicationResume, + refetchOnWindowFocus: refetchOnWindowFocus, ); return infiniteQuery; } @@ -478,7 +552,18 @@ class QueryBowlScope extends InheritedWidget { required this.bowl, required Widget child, Key? key, - }) : super(key: key, child: child); + }) : super( + key: key, + child: MouseRegion( + onEnter: (event) { + bowl.notifyWindowFocused(); + }, + onExit: (event) { + bowl.updateWeLostTheApp(); + }, + child: child, + ), + ); @override bool updateShouldNotify(covariant oldWidget) => false; diff --git a/packages/fl_query/lib/src/utils.dart b/packages/fl_query/lib/src/utils.dart index 08bc52c..7040982 100644 --- a/packages/fl_query/lib/src/utils.dart +++ b/packages/fl_query/lib/src/utils.dart @@ -1,6 +1,8 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:fl_query/src/query.dart'; import 'package:uuid/uuid.dart'; +import 'dart:io' show Platform; +import 'package:flutter/foundation.dart'; Future callQueryListeners(Set> listeners, T data) { return Future.wait(listeners.map( @@ -54,3 +56,14 @@ bool isConnectedToInternet(ConnectivityResult result) { String getVariable(String queryKey) { return queryKey.split("#").last; } + +final kIsDesktop = kIsLinux || kIsWindows || kIsMacOS; + +final kIsMobile = kIsAndroid || kIsIOS; + +final kIsMacOS = kIsWeb ? false : Platform.isMacOS; + +final kIsLinux = kIsWeb ? false : Platform.isLinux; +final kIsAndroid = kIsWeb ? false : Platform.isAndroid; +final kIsIOS = kIsWeb ? false : Platform.isIOS; +final kIsWindows = kIsWeb ? false : Platform.isWindows; diff --git a/packages/fl_query/pubspec.yaml b/packages/fl_query/pubspec.yaml index 2afa7c5..b9f8aec 100644 --- a/packages/fl_query/pubspec.yaml +++ b/packages/fl_query/pubspec.yaml @@ -14,7 +14,7 @@ environment: dependencies: collection: ^1.16.0 - connectivity_plus: ^2.3.5 + connectivity_plus: ^2.3.9 flutter: sdk: flutter queue: ^3.1.0+1