watchCourseTable method
- required int semesterId,
Watches the course schedule for a semester with automatic background refresh.
Emits cached data immediately, then triggers a background network fetch if data is empty or stale. The stream re-emits automatically when the DB is updated.
Use watchCourseOffering for related data (teachers, classrooms, schedules).
Implementation
Stream<CourseTableData> watchCourseTable({required int semesterId}) async* {
const ttl = Duration(days: 3);
final query = _database.select(_database.courseTableSlots)
..where((s) => s.semester.equals(semesterId));
await for (final rows in query.watch()) {
final allOfferingRows =
await (_database.select(
_database.courseOfferings,
)..where((o) => o.semester.equals(semesterId))).join([
leftOuterJoin(
_database.courses,
_database.courses.code.equalsExp(
_database.courseOfferings.courseCode,
),
),
]).get();
final allOfferings = allOfferingRows.map((row) {
final offering = row.readTable(_database.courseOfferings);
final course = row.readTableOrNull(_database.courses);
return (offering: offering, course: course);
}).toList();
final data = _buildCourseTableData(rows, allOfferings);
if (data.scheduled.isEmpty && data.unscheduled.isEmpty) {
try {
await refreshCourseTable(semesterId: semesterId);
} catch (_) {
// Absorb: yield empty below so UI exits loading state
}
}
yield data;
final semesterRow = await (_database.select(
_database.semesters,
)..where((s) => s.id.equals(semesterId))).getSingleOrNull();
if (semesterRow == null) return;
final age = switch (semesterRow.courseTableFetchedAt) {
final t? => DateTime.now().difference(t),
null => ttl,
};
if (age >= ttl) {
try {
await refreshCourseTable(semesterId: semesterId);
} catch (_) {
// Absorb: stale data is shown via stream
}
}
}
}