11import 'dart:async' ;
22
3+ import 'package:collection/collection.dart' ;
4+ import 'package:gql_exec/gql_exec.dart' ;
35import 'package:graphql/src/core/query_manager.dart' ;
46import 'package:graphql/src/core/query_options.dart' ;
57import 'package:graphql/src/core/observable_query.dart' ;
@@ -8,9 +10,11 @@ import 'package:graphql/src/core/observable_query.dart';
810class QueryScheduler {
911 QueryScheduler ({
1012 this .queryManager,
11- });
13+ bool deduplicatePollers = false ,
14+ }) : _deduplicatePollers = deduplicatePollers;
1215
1316 QueryManager ? queryManager;
17+ final bool _deduplicatePollers;
1418
1519 /// Map going from query ids to the [WatchQueryOptions] associated with those queries.
1620 Map <String , WatchQueryOptions > registeredQueries =
@@ -68,25 +72,81 @@ class QueryScheduler {
6872 options.pollInterval != null && options.pollInterval! > Duration .zero,
6973 );
7074
71- registeredQueries[queryId] = options;
75+ final existingEntry = _fastestEntryForRequest (options.asRequest);
76+ final String ? existingQueryId = existingEntry? .key;
77+ final Duration ? existingInterval = existingEntry? .value.pollInterval;
7278
73- final interval = options.pollInterval;
79+ // Update or add the query in registeredQueries
80+ registeredQueries[queryId] = options;
7481
75- if (intervalQueries.containsKey (interval)) {
76- intervalQueries[interval]! .add (queryId);
82+ final Duration interval;
83+
84+ if (existingInterval != null && _deduplicatePollers) {
85+ if (existingInterval > options.pollInterval! ) {
86+ // The new one is faster, remove the old one and add the new one
87+ intervalQueries[existingInterval]! .remove (existingQueryId);
88+ interval = options.pollInterval! ;
89+ } else {
90+ // The new one is slower or the same. Don't add it to the list
91+ return ;
92+ }
7793 } else {
78- intervalQueries[interval] = Set <String >.of ([queryId]);
79-
80- _pollingTimers[interval] = Timer .periodic (
81- interval! ,
82- (Timer timer) => fetchQueriesOnInterval (timer, interval),
83- );
94+ // If there is no existing interval, we'll add the new one
95+ interval = options.pollInterval! ;
8496 }
97+
98+ // Add new query to intervalQueries
99+ _addInterval (queryId, interval);
85100 }
86101
87102 /// Removes the [ObservableQuery] from one of the registered queries.
88103 /// The fetchQueriesOnInterval will then take care of not firing it anymore.
89104 void stopPollingQuery (String queryId) {
90- registeredQueries.remove (queryId);
105+ final removedQuery = registeredQueries.remove (queryId);
106+
107+ if (removedQuery == null ||
108+ removedQuery.pollInterval != null ||
109+ ! _deduplicatePollers) {
110+ return ;
111+ }
112+
113+ // If there is a registered query that has the same `asRequest` as this one
114+ // Add the next fastest duration to the intervalQueries
115+ final fastestEntry = _fastestEntryForRequest (removedQuery.asRequest);
116+ final String ? fastestQueryId = fastestEntry? .key;
117+ final Duration ? fastestInterval = fastestEntry? .value.pollInterval;
118+
119+ if (fastestQueryId == null || fastestInterval == null ) {
120+ // There is no other query, return.
121+ return ;
122+ }
123+
124+ _addInterval (fastestQueryId, fastestInterval);
125+ }
126+
127+ /// Adds a [queryId] to the [intervalQueries] for a specific [interval]
128+ /// and starts the timer if it doesn't exist.
129+ void _addInterval (String queryId, Duration interval) {
130+ final existingSet = intervalQueries[interval];
131+ if (existingSet != null ) {
132+ existingSet.add (queryId);
133+ } else {
134+ intervalQueries[interval] = {queryId};
135+ _pollingTimers[interval] = Timer .periodic (
136+ interval, (Timer timer) => fetchQueriesOnInterval (timer, interval));
137+ }
138+ }
139+
140+ /// Returns the fastest query that matches the [request] or null if none exists.
141+ MapEntry <String , WatchQueryOptions <Object ?>>? _fastestEntryForRequest (
142+ Request request) {
143+ return registeredQueries.entries
144+ // All existing queries mapping to the same request.
145+ .where ((entry) =>
146+ entry.value.asRequest == request &&
147+ entry.value.pollInterval != null )
148+ // Ascending is default (shortest poll interval first)
149+ .sortedBy ((entry) => entry.value.pollInterval! )
150+ .firstOrNull;
91151 }
92152}
0 commit comments