watchStudents method
- int courseOfferingId
Watches the I-School Plus roster (classmates) for a course offering.
Emits the cached roster immediately (ordered by student ID), then triggers a background network fetch if the roster is empty or stale. The stream re-emits automatically when the DB is updated.
Network errors during background refresh are absorbed — the stream continues showing stale (or empty) data rather than erroring.
Implementation
Stream<List<Student>> watchStudents(int courseOfferingId) async* {
const ttl = Duration(days: 1);
final query =
_database.select(_database.courseOfferingStudents).join([
innerJoin(
_database.students,
_database.students.id.equalsExp(
_database.courseOfferingStudents.student,
),
),
])
..where(
_database.courseOfferingStudents.courseOffering.equals(
courseOfferingId,
),
)
..orderBy([OrderingTerm.asc(_database.students.studentId)]);
await for (final rows in query.watch()) {
final students = [
for (final row in rows) row.readTable(_database.students),
];
if (students.isEmpty) {
try {
await refreshStudents(courseOfferingId);
} catch (_) {
// Absorb: yield empty below so UI exits loading state
}
}
yield students;
final offering = await (_database.select(
_database.courseOfferings,
)..where((o) => o.id.equals(courseOfferingId))).getSingleOrNull();
if (offering == null) return;
final age = switch (offering.rosterFetchedAt) {
final t? => DateTime.now().difference(t),
null => ttl,
};
if (age >= ttl) {
try {
await refreshStudents(courseOfferingId);
} catch (_) {
// Absorb: stale data is shown via stream
}
}
}
}