Skip to content

Commit 2f869c4

Browse files
committed
msglist [nfc]: Unify fetch/cooldown status as busyFetchingMore
Now the distinction between these two states exists only for asserts.
1 parent 245ae80 commit 2f869c4

File tree

3 files changed

+51
-67
lines changed

3 files changed

+51
-67
lines changed

lib/model/message_list.dart

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -125,28 +125,18 @@ mixin _MessageSequence {
125125
bool get haveOldest => _haveOldest;
126126
bool _haveOldest = false;
127127

128-
/// Whether we are currently fetching the next batch of older messages.
128+
/// Whether this message list is currently busy when it comes to
129+
/// fetching more messages.
129130
///
130-
/// When this is true, [fetchOlder] is a no-op.
131-
/// That method is called frequently by Flutter's scrolling logic,
132-
/// and this field helps us avoid spamming the same request just to get
133-
/// the same response each time.
134-
///
135-
/// See also [fetchOlderCoolingDown].
136-
bool get fetchingOlder => _status == FetchingStatus.fetchOlder;
137-
138-
/// Whether [fetchOlder] had a request error recently.
139-
///
140-
/// When this is true, [fetchOlder] is a no-op.
141-
/// That method is called frequently by Flutter's scrolling logic,
142-
/// and this field mitigates spamming the same request and getting
143-
/// the same error each time.
144-
///
145-
/// "Recently" is decided by a [BackoffMachine] that resets
146-
/// when a [fetchOlder] request succeeds.
147-
///
148-
/// See also [fetchingOlder].
149-
bool get fetchOlderCoolingDown => _status == FetchingStatus.fetchOlderCoolingDown;
131+
/// Here "busy" means a new call to fetch more messages would do nothing,
132+
/// rather than make any request to the server,
133+
/// as a result of an existing recent request.
134+
/// This is true both when the recent request is still outstanding,
135+
/// and when it failed and the backoff from that is still in progress.
136+
bool get busyFetchingMore => switch (_status) {
137+
FetchingStatus.fetchOlder || FetchingStatus.fetchOlderCoolingDown => true,
138+
_ => false,
139+
};
150140

151141
FetchingStatus _status = FetchingStatus.unstarted;
152142

@@ -167,7 +157,7 @@ mixin _MessageSequence {
167157
/// before, between, or after the messages.
168158
///
169159
/// This information is completely derived from [messages] and
170-
/// the flags [haveOldest], [fetchingOlder] and [fetchOlderCoolingDown].
160+
/// the flags [haveOldest] and [busyFetchingMore].
171161
/// It exists as an optimization, to memoize that computation.
172162
///
173163
/// See also [middleItem], an index which divides this list
@@ -507,7 +497,7 @@ class MessageListView with ChangeNotifier, _MessageSequence {
507497
Future<void> fetchInitial() async {
508498
// TODO(#80): fetch from anchor firstUnread, instead of newest
509499
// TODO(#82): fetch from a given message ID as anchor
510-
assert(!fetched && !haveOldest && !fetchingOlder && !fetchOlderCoolingDown);
500+
assert(!fetched && !haveOldest && !busyFetchingMore);
511501
assert(messages.isEmpty && contents.isEmpty);
512502
assert(_status == FetchingStatus.unstarted);
513503
_status = FetchingStatus.fetchInitial;
@@ -573,10 +563,16 @@ class MessageListView with ChangeNotifier, _MessageSequence {
573563
}
574564

575565
/// Fetch the next batch of older messages, if applicable.
566+
///
567+
/// If there are no older messages to fetch (i.e. if [haveOldest]),
568+
/// or if this message list is already busy fetching more messages
569+
/// (i.e. if [busyFetchingMore], which includes backoff from failed requests),
570+
/// then this method does nothing and immediately returns.
571+
/// That makes this method suitable to call frequently, e.g. every frame,
572+
/// whenever it looks likely to be useful to have more messages.
576573
Future<void> fetchOlder() async {
577574
if (haveOldest) return;
578-
if (fetchingOlder) return;
579-
if (fetchOlderCoolingDown) return;
575+
if (busyFetchingMore) return;
580576
assert(fetched);
581577
assert(narrow is! TopicNarrow
582578
// We only intend to send "with" in [fetchInitial]; see there.

lib/widgets/message_list.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -691,13 +691,8 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
691691
// in backoff from it; and even if the fetch is/was for the other direction.
692692
// The loading indicator really means "busy, working on it"; and that's the
693693
// right summary even if the fetch is internally queued behind other work.
694-
695-
// (This assertion is an invariant of [MessageListView].)
696-
assert(!(model.fetchingOlder && model.fetchOlderCoolingDown));
697-
final busyFetchingMore =
698-
model.fetchingOlder || model.fetchOlderCoolingDown;
699694
return model.haveOldest ? const _MessageListHistoryStart()
700-
: busyFetchingMore ? const _MessageListLoadingMore()
695+
: model.busyFetchingMore ? const _MessageListLoadingMore()
701696
: const SizedBox.shrink();
702697
}
703698

test/model/message_list_test.dart

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,12 @@ void main() {
256256
).toJson());
257257
final fetchFuture = model.fetchOlder();
258258
checkNotifiedOnce();
259-
check(model).fetchingOlder.isTrue();
259+
check(model).busyFetchingMore.isTrue();
260260

261261
await fetchFuture;
262262
checkNotifiedOnce();
263263
check(model)
264-
..fetchingOlder.isFalse()
264+
..busyFetchingMore.isFalse()
265265
..messages.length.equals(200);
266266
checkLastRequest(
267267
narrow: narrow.apiEncode(),
@@ -285,20 +285,20 @@ void main() {
285285
).toJson());
286286
final fetchFuture = model.fetchOlder();
287287
checkNotifiedOnce();
288-
check(model).fetchingOlder.isTrue();
288+
check(model).busyFetchingMore.isTrue();
289289

290290
// Don't prepare another response.
291291
final fetchFuture2 = model.fetchOlder();
292292
checkNotNotified();
293-
check(model).fetchingOlder.isTrue();
293+
check(model).busyFetchingMore.isTrue();
294294

295295
await fetchFuture;
296296
await fetchFuture2;
297297
// We must not have made another request, because we didn't
298298
// prepare another response and didn't get an exception.
299299
checkNotifiedOnce();
300300
check(model)
301-
..fetchingOlder.isFalse()
301+
..busyFetchingMore.isFalse()
302302
..messages.length.equals(200);
303303
});
304304

@@ -330,18 +330,17 @@ void main() {
330330
check(async.pendingTimers).isEmpty();
331331
await check(model.fetchOlder()).throws<ZulipApiException>();
332332
checkNotified(count: 2);
333-
check(model).fetchOlderCoolingDown.isTrue();
333+
check(model).busyFetchingMore.isTrue();
334334
check(connection.takeRequests()).single;
335335

336336
await model.fetchOlder();
337337
checkNotNotified();
338-
check(model).fetchOlderCoolingDown.isTrue();
339-
check(model).fetchingOlder.isFalse();
338+
check(model).busyFetchingMore.isTrue();
340339
check(connection.lastRequest).isNull();
341340

342341
// Wait long enough that a first backoff is sure to finish.
343342
async.elapse(const Duration(seconds: 1));
344-
check(model).fetchOlderCoolingDown.isFalse();
343+
check(model).busyFetchingMore.isFalse();
345344
checkNotifiedOnce();
346345
check(connection.lastRequest).isNull();
347346

@@ -366,7 +365,7 @@ void main() {
366365
await model.fetchOlder();
367366
checkNotified(count: 2);
368367
check(model)
369-
..fetchingOlder.isFalse()
368+
..busyFetchingMore.isFalse()
370369
..messages.length.equals(200);
371370
});
372371

@@ -1068,7 +1067,7 @@ void main() {
10681067
messages: olderMessages,
10691068
).toJson());
10701069
final fetchFuture = model.fetchOlder();
1071-
check(model).fetchingOlder.isTrue();
1070+
check(model).busyFetchingMore.isTrue();
10721071
checkHasMessages(initialMessages);
10731072
checkNotifiedOnce();
10741073

@@ -1081,7 +1080,7 @@ void main() {
10811080
origStreamId: otherStream.streamId,
10821081
newMessages: movedMessages,
10831082
));
1084-
check(model).fetchingOlder.isFalse();
1083+
check(model).busyFetchingMore.isFalse();
10851084
checkHasMessages([]);
10861085
checkNotifiedOnce();
10871086

@@ -1104,7 +1103,7 @@ void main() {
11041103
).toJson());
11051104
final fetchFuture = model.fetchOlder();
11061105
checkHasMessages(initialMessages);
1107-
check(model).fetchingOlder.isTrue();
1106+
check(model).busyFetchingMore.isTrue();
11081107
checkNotifiedOnce();
11091108

11101109
connection.prepare(delay: const Duration(seconds: 1), json: newestResult(
@@ -1117,7 +1116,7 @@ void main() {
11171116
newMessages: movedMessages,
11181117
));
11191118
checkHasMessages([]);
1120-
check(model).fetchingOlder.isFalse();
1119+
check(model).busyFetchingMore.isFalse();
11211120
checkNotifiedOnce();
11221121

11231122
async.elapse(const Duration(seconds: 1));
@@ -1138,7 +1137,7 @@ void main() {
11381137
BackoffMachine.debugDuration = const Duration(seconds: 1);
11391138
await check(model.fetchOlder()).throws<ZulipApiException>();
11401139
final backoffTimerA = async.pendingTimers.single;
1141-
check(model).fetchOlderCoolingDown.isTrue();
1140+
check(model).busyFetchingMore.isTrue();
11421141
check(model).fetched.isTrue();
11431142
checkHasMessages(initialMessages);
11441143
checkNotified(count: 2);
@@ -1156,36 +1155,36 @@ void main() {
11561155
check(model).fetched.isFalse();
11571156
checkHasMessages([]);
11581157
checkNotifiedOnce();
1159-
check(model).fetchOlderCoolingDown.isFalse();
1158+
check(model).busyFetchingMore.isFalse();
11601159
check(backoffTimerA.isActive).isTrue();
11611160

11621161
async.elapse(Duration.zero);
11631162
check(model).fetched.isTrue();
11641163
checkHasMessages(initialMessages + movedMessages);
11651164
checkNotifiedOnce();
1166-
check(model).fetchOlderCoolingDown.isFalse();
1165+
check(model).busyFetchingMore.isFalse();
11671166
check(backoffTimerA.isActive).isTrue();
11681167

11691168
connection.prepare(apiException: eg.apiBadRequest());
11701169
BackoffMachine.debugDuration = const Duration(seconds: 2);
11711170
await check(model.fetchOlder()).throws<ZulipApiException>();
11721171
final backoffTimerB = async.pendingTimers.last;
1173-
check(model).fetchOlderCoolingDown.isTrue();
1172+
check(model).busyFetchingMore.isTrue();
11741173
check(backoffTimerA.isActive).isTrue();
11751174
check(backoffTimerB.isActive).isTrue();
11761175
checkNotified(count: 2);
11771176

1178-
// When `backoffTimerA` ends, `fetchOlderCoolingDown` remains `true`
1177+
// When `backoffTimerA` ends, `busyFetchingMore` remains `true`
11791178
// because the backoff was from a previous generation.
11801179
async.elapse(const Duration(seconds: 1));
1181-
check(model).fetchOlderCoolingDown.isTrue();
1180+
check(model).busyFetchingMore.isTrue();
11821181
check(backoffTimerA.isActive).isFalse();
11831182
check(backoffTimerB.isActive).isTrue();
11841183
checkNotNotified();
11851184

1186-
// When `backoffTimerB` ends, `fetchOlderCoolingDown` gets reset.
1185+
// When `backoffTimerB` ends, `busyFetchingMore` gets reset.
11871186
async.elapse(const Duration(seconds: 1));
1188-
check(model).fetchOlderCoolingDown.isFalse();
1187+
check(model).busyFetchingMore.isFalse();
11891188
check(backoffTimerA.isActive).isFalse();
11901189
check(backoffTimerB.isActive).isFalse();
11911190
checkNotifiedOnce();
@@ -1267,7 +1266,7 @@ void main() {
12671266
).toJson());
12681267
final fetchFuture1 = model.fetchOlder();
12691268
checkHasMessages(initialMessages);
1270-
check(model).fetchingOlder.isTrue();
1269+
check(model).busyFetchingMore.isTrue();
12711270
checkNotifiedOnce();
12721271

12731272
connection.prepare(delay: const Duration(seconds: 1), json: newestResult(
@@ -1280,7 +1279,7 @@ void main() {
12801279
newMessages: movedMessages,
12811280
));
12821281
checkHasMessages([]);
1283-
check(model).fetchingOlder.isFalse();
1282+
check(model).busyFetchingMore.isFalse();
12841283
checkNotifiedOnce();
12851284

12861285
async.elapse(const Duration(seconds: 1));
@@ -1293,19 +1292,19 @@ void main() {
12931292
).toJson());
12941293
final fetchFuture2 = model.fetchOlder();
12951294
checkHasMessages(initialMessages + movedMessages);
1296-
check(model).fetchingOlder.isTrue();
1295+
check(model).busyFetchingMore.isTrue();
12971296
checkNotifiedOnce();
12981297

12991298
await fetchFuture1;
13001299
checkHasMessages(initialMessages + movedMessages);
13011300
// The older fetchOlder call should not override fetchingOlder set by
13021301
// the new fetchOlder call, nor should it notify the listeners.
1303-
check(model).fetchingOlder.isTrue();
1302+
check(model).busyFetchingMore.isTrue();
13041303
checkNotNotified();
13051304

13061305
await fetchFuture2;
13071306
checkHasMessages(olderMessages + initialMessages + movedMessages);
1308-
check(model).fetchingOlder.isFalse();
1307+
check(model).busyFetchingMore.isFalse();
13091308
checkNotifiedOnce();
13101309
}));
13111310
});
@@ -2140,15 +2139,10 @@ void checkInvariants(MessageListView model) {
21402139
check(model)
21412140
..messages.isEmpty()
21422141
..haveOldest.isFalse()
2143-
..fetchingOlder.isFalse()
2144-
..fetchOlderCoolingDown.isFalse();
2142+
..busyFetchingMore.isFalse();
21452143
}
21462144
if (model.haveOldest) {
2147-
check(model).fetchingOlder.isFalse();
2148-
check(model).fetchOlderCoolingDown.isFalse();
2149-
}
2150-
if (model.fetchingOlder) {
2151-
check(model).fetchOlderCoolingDown.isFalse();
2145+
check(model).busyFetchingMore.isFalse();
21522146
}
21532147

21542148
for (final message in model.messages) {
@@ -2281,6 +2275,5 @@ extension MessageListViewChecks on Subject<MessageListView> {
22812275
Subject<int> get middleItem => has((x) => x.middleItem, 'middleItem');
22822276
Subject<bool> get fetched => has((x) => x.fetched, 'fetched');
22832277
Subject<bool> get haveOldest => has((x) => x.haveOldest, 'haveOldest');
2284-
Subject<bool> get fetchingOlder => has((x) => x.fetchingOlder, 'fetchingOlder');
2285-
Subject<bool> get fetchOlderCoolingDown => has((x) => x.fetchOlderCoolingDown, 'fetchOlderCoolingDown');
2278+
Subject<bool> get busyFetchingMore => has((x) => x.busyFetchingMore, 'busyFetchingMore');
22862279
}

0 commit comments

Comments
 (0)