watchApplicationCatalog method
Watches the current user's cached application catalog.
Cached data and favorite changes are emitted through the same Drift stream. Missing data is fetched before the first empty value is yielded; stale data is yielded immediately and refreshed in the background. Network errors are absorbed so cached data remains available.
Implementation
Stream<List<PortalApplicationCategoryData>> watchApplicationCatalog() async* {
const ttl = Duration(days: 1);
final user = await _database.select(_database.users).getSingleOrNull();
if (user == null) {
yield const [];
return;
}
var skipNextStalenessCheck = user.applicationCatalogFetchedAt == null;
if (skipNextStalenessCheck) {
try {
await refreshApplicationCatalog();
} catch (_) {
// Absorb: the initial query snapshot below yields empty or stale data.
}
}
final categories = _database.portalApplicationCategories;
final applications = _database.portalApplications;
final favorites = _database.portalApplicationFavorites;
final query =
_database.select(categories).join([
leftOuterJoin(
applications,
applications.category.equalsExp(categories.id),
),
leftOuterJoin(
favorites,
favorites.user.equalsExp(categories.user) &
favorites.applicationCode.equalsExp(applications.code),
),
])
..where(categories.user.equals(user.id))
..orderBy([
OrderingTerm.asc(categories.position),
OrderingTerm.asc(applications.position),
]);
await for (final rows in query.watch()) {
final data = _mapCatalogRows(rows);
final currentUser = await (_database.select(
_database.users,
)..where((row) => row.id.equals(user.id))).getSingleOrNull();
if (currentUser == null) {
yield const [];
return;
}
yield data;
if (skipNextStalenessCheck) {
skipNextStalenessCheck = false;
continue;
}
final freshUser = await (_database.select(
_database.users,
)..where((row) => row.id.equals(user.id))).getSingleOrNull();
final age = switch (freshUser?.applicationCatalogFetchedAt) {
final fetchedAt? => DateTime.now().difference(fetchedAt),
null => ttl,
};
if (age >= ttl) {
try {
await refreshApplicationCatalog();
} catch (_) {
// Absorb: stale data is already visible through the stream.
}
}
}
}