1
1
import 'dart:async' ;
2
2
3
+ import 'package:collection/collection.dart' ;
4
+ import 'package:gql_exec/gql_exec.dart' ;
3
5
import 'package:graphql/src/core/query_manager.dart' ;
4
6
import 'package:graphql/src/core/query_options.dart' ;
5
7
import 'package:graphql/src/core/observable_query.dart' ;
@@ -8,9 +10,11 @@ import 'package:graphql/src/core/observable_query.dart';
8
10
class QueryScheduler {
9
11
QueryScheduler ({
10
12
this .queryManager,
11
- });
13
+ bool deduplicatePollers = false ,
14
+ }) : _deduplicatePollers = deduplicatePollers;
12
15
13
16
QueryManager ? queryManager;
17
+ final bool _deduplicatePollers;
14
18
15
19
/// Map going from query ids to the [WatchQueryOptions] associated with those queries.
16
20
Map <String , WatchQueryOptions > registeredQueries =
@@ -68,25 +72,81 @@ class QueryScheduler {
68
72
options.pollInterval != null && options.pollInterval! > Duration .zero,
69
73
);
70
74
71
- registeredQueries[queryId] = options;
75
+ final existingEntry = _fastestEntryForRequest (options.asRequest);
76
+ final String ? existingQueryId = existingEntry? .key;
77
+ final Duration ? existingInterval = existingEntry? .value.pollInterval;
72
78
73
- final interval = options.pollInterval;
79
+ // Update or add the query in registeredQueries
80
+ registeredQueries[queryId] = options;
74
81
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
+ }
77
93
} 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! ;
84
96
}
97
+
98
+ // Add new query to intervalQueries
99
+ _addInterval (queryId, interval);
85
100
}
86
101
87
102
/// Removes the [ObservableQuery] from one of the registered queries.
88
103
/// The fetchQueriesOnInterval will then take care of not firing it anymore.
89
104
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;
91
151
}
92
152
}
0 commit comments