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,9 +567,21 @@ 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
_fetchingOlder = false ;
574
+ if (hasFetchError) {
575
+ assert (! fetchOlderCoolingDown);
576
+ _fetchOlderCoolingDown = true ;
577
+ unawaited ((_fetchOlderCooldownBackoffMachine ?? = BackoffMachine ())
578
+ .wait ().then ((_) {
579
+ if (this .generation != generation) return ;
580
+ _fetchOlderCoolingDown = false ;
581
+ _updateEndMarkers ();
582
+ notifyListeners ();
583
+ }));
584
+ }
535
585
_updateEndMarkers ();
536
586
notifyListeners ();
537
587
}
0 commit comments