1
+ import 'dart:async' ;
2
+
1
3
import 'package:collection/collection.dart' ;
2
4
import 'package:flutter/foundation.dart' ;
3
5
6
+ import '../api/backoff.dart' ;
4
7
import '../api/model/events.dart' ;
5
8
import '../api/model/model.dart' ;
6
9
import '../api/route/messages.dart' ;
@@ -89,9 +92,32 @@ mixin _MessageSequence {
89
92
bool _haveOldest = false ;
90
93
91
94
/// Whether we are currently fetching the next batch of older messages.
95
+ ///
96
+ /// When this is true, [fetchOlder] is a no-op.
97
+ /// That method is called frequently by Flutter's scrolling logic,
98
+ /// and this field helps us avoid spamming the same request just to get
99
+ /// the same response each time.
100
+ ///
101
+ /// See also [fetchOlderCoolingDown] .
92
102
bool get fetchingOlder => _fetchingOlder;
93
103
bool _fetchingOlder = false ;
94
104
105
+ /// Whether [fetchOlder] had a request error recently.
106
+ ///
107
+ /// When this is true, [fetchOlder] is a no-op.
108
+ /// That method is called frequently by Flutter's scrolling logic,
109
+ /// and this field mitigates spamming the same request and getting
110
+ /// the same error each time.
111
+ ///
112
+ /// "Recently" is decided by a [BackoffMachine] that resets
113
+ /// when a [fetchOlder] request succeeds.
114
+ ///
115
+ /// See also [fetchingOlder] .
116
+ bool get fetchOlderCoolingDown => _fetchOlderCoolingDown;
117
+ bool _fetchOlderCoolingDown = false ;
118
+
119
+ BackoffMachine ? _fetchOlderCooldownBackoffMachine;
120
+
95
121
/// The parsed message contents, as a list parallel to [messages] .
96
122
///
97
123
/// The i'th element is the result of parsing the i'th element of [messages] .
@@ -107,7 +133,7 @@ mixin _MessageSequence {
107
133
/// before, between, or after the messages.
108
134
///
109
135
/// This information is completely derived from [messages] and
110
- /// the flags [haveOldest] and [fetchingOlder ] .
136
+ /// the flags [haveOldest] , [fetchingOlder] and [fetchOlderCoolingDown ] .
111
137
/// It exists as an optimization, to memoize that computation.
112
138
final QueueList <MessageListItem > items = QueueList ();
113
139
@@ -241,6 +267,8 @@ mixin _MessageSequence {
241
267
_fetched = false ;
242
268
_haveOldest = false ;
243
269
_fetchingOlder = false ;
270
+ _fetchOlderCoolingDown = false ;
271
+ _fetchOlderCooldownBackoffMachine = null ;
244
272
contents.clear ();
245
273
items.clear ();
246
274
}
@@ -290,10 +318,12 @@ mixin _MessageSequence {
290
318
void _updateEndMarkers () {
291
319
assert (fetched);
292
320
assert (! (haveOldest && fetchingOlder));
293
- final startMarker = switch ((fetchingOlder, haveOldest)) {
294
- (true , _) => const MessageListLoadingItem (MessageListDirection .older),
295
- (_, true ) => const MessageListHistoryStartItem (),
296
- (_, _) => null ,
321
+ assert (! (fetchingOlder && fetchOlderCoolingDown));
322
+ final startMarker = switch ((fetchingOlder, haveOldest, fetchOlderCoolingDown)) {
323
+ (true , _, _) => const MessageListLoadingItem (MessageListDirection .older),
324
+ (_, true , _) => const MessageListHistoryStartItem (),
325
+ (_, _, true ) => const MessageListLoadingItem (MessageListDirection .older),
326
+ (_, _, _) => null ,
297
327
};
298
328
final hasStartMarker = switch (items.firstOrNull) {
299
329
MessageListLoadingItem () => true ,
@@ -470,7 +500,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
470
500
Future <void > fetchInitial () async {
471
501
// TODO(#80): fetch from anchor firstUnread, instead of newest
472
502
// TODO(#82): fetch from a given message ID as anchor
473
- assert (! fetched && ! haveOldest && ! fetchingOlder);
503
+ assert (! fetched && ! haveOldest && ! fetchingOlder && ! fetchOlderCoolingDown );
474
504
assert (messages.isEmpty && contents.isEmpty);
475
505
// TODO schedule all this in another isolate
476
506
final generation = this .generation;
@@ -498,20 +528,28 @@ class MessageListView with ChangeNotifier, _MessageSequence {
498
528
Future <void > fetchOlder () async {
499
529
if (haveOldest) return ;
500
530
if (fetchingOlder) return ;
531
+ if (fetchOlderCoolingDown) return ;
501
532
assert (fetched);
502
533
assert (messages.isNotEmpty);
503
534
_fetchingOlder = true ;
504
535
_updateEndMarkers ();
505
536
notifyListeners ();
506
537
final generation = this .generation;
538
+ bool hasFetchError = false ;
507
539
try {
508
- final result = await getMessages (store.connection,
509
- narrow: narrow.apiEncode (),
510
- anchor: NumericAnchor (messages[0 ].id),
511
- includeAnchor: false ,
512
- numBefore: kMessageListFetchBatchSize,
513
- numAfter: 0 ,
514
- );
540
+ final GetMessagesResult result;
541
+ try {
542
+ result = await getMessages (store.connection,
543
+ narrow: narrow.apiEncode (),
544
+ anchor: NumericAnchor (messages[0 ].id),
545
+ includeAnchor: false ,
546
+ numBefore: kMessageListFetchBatchSize,
547
+ numAfter: 0 ,
548
+ );
549
+ } catch (e) {
550
+ hasFetchError = true ;
551
+ rethrow ;
552
+ }
515
553
if (this .generation > generation) return ;
516
554
517
555
if (result.messages.isNotEmpty
@@ -529,6 +567,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
529
567
530
568
_insertAllMessages (0 , fetchedMessages);
531
569
_haveOldest = result.foundOldest;
570
+ _fetchOlderCooldownBackoffMachine = null ;
532
571
} finally {
533
572
if (this .generation != generation) {
534
573
// This lint rule is more helpful for flagging confusing uses of
@@ -540,6 +579,17 @@ class MessageListView with ChangeNotifier, _MessageSequence {
540
579
return ;
541
580
}
542
581
_fetchingOlder = false ;
582
+ if (hasFetchError) {
583
+ assert (! fetchOlderCoolingDown);
584
+ _fetchOlderCoolingDown = true ;
585
+ unawaited ((_fetchOlderCooldownBackoffMachine ?? = BackoffMachine ())
586
+ .wait ().then ((_) {
587
+ if (this .generation != generation) return ;
588
+ _fetchOlderCoolingDown = false ;
589
+ _updateEndMarkers ();
590
+ notifyListeners ();
591
+ }));
592
+ }
543
593
_updateEndMarkers ();
544
594
notifyListeners ();
545
595
}
0 commit comments