watchCalendarEvents method
Watches calendar events overlapping the given date range.
Reads events from the local DB and re-emits when the DB is updated. If the cached window is missing, stale, or doesn't cover the requested range (e.g., semester list just expanded), this stream awaits a refresh before yielding so it can populate or update the cache.
Network errors during refresh are absorbed — the stream continues showing stale (or empty) data rather than erroring.
Implementation
Stream<List<CalendarEvent>> watchCalendarEvents({
required DateTime startDate,
required DateTime endDate,
}) async* {
const ttl = Duration(days: 1);
// Gate: refreshCalendarEvents uses delete+insert, which reassigns
// autoincrement IDs. Drift .watch() re-emits on every ID change, which
// would re-trigger this branch forever for permanently out-of-window
// requests that no refresh can ever cover.
var refreshedForOutOfWindow = false;
// Lazily computed and cached; re-read once when out-of-window to catch
// semesters that landed after the stream started.
(DateTime, DateTime)? window;
await for (final events in _eventsOverlapping(startDate, endDate).watch()) {
final user = await _database.select(_database.users).getSingleOrNull();
if (user == null) {
yield [];
continue;
}
window ??= await _computeWindow();
var (windowStart, windowEnd) = window;
var outOfWindow =
startDate.isBefore(windowStart) || endDate.isAfter(windowEnd);
// If out of window, re-compute once in case the semester list expanded
// since the stream started.
if (outOfWindow && !refreshedForOutOfWindow) {
window = await _computeWindow();
(windowStart, windowEnd) = window;
outOfWindow =
startDate.isBefore(windowStart) || endDate.isAfter(windowEnd);
}
// calendarFetchedAt is authoritative for cache presence within the
// window. For out-of-window requests, try once in case the window has
// widened since the cached stamp (e.g., a newly enrolled semester).
final shouldRefresh =
user.calendarFetchedAt == null ||
(outOfWindow && !refreshedForOutOfWindow);
if (shouldRefresh) {
if (outOfWindow) refreshedForOutOfWindow = true;
try {
await refreshCalendarEvents();
} catch (_) {
// Absorb: yield below so UI exits loading state
}
}
yield events;
final freshUser = await _database
.select(_database.users)
.getSingleOrNull();
final age = switch (freshUser?.calendarFetchedAt) {
final t? => DateTime.now().difference(t),
null => ttl,
};
if (age >= ttl) {
try {
await refreshCalendarEvents();
} catch (_) {
// Absorb: stale data is shown via stream
}
}
}
}