entitlementInvalidationWatcher function
- @Riverpod(keepAlive: true)
Background watcher that subscribes to Supabase Realtime on the
admin_grants and subscriptions tables and invalidates the cached
currentEntitlementsProvider on any change. RLS still filters which
rows the client receives — only grants/subs targeting the current user
(or their org, as an admin) will trigger an event.
The watcher is lifecycle-managed: it subscribes on the first read, tears down on provider dispose, and rebuilds when auth state changes so a sign-in/sign-out cleanly swaps the user context.
Wire into the app by reading the provider once at startup (e.g. from
main.dart's _initServicesAsync). Reading it is enough — the side
effect is the subscription.
Implementation
@Riverpod(keepAlive: true)
Future<void> entitlementInvalidationWatcher(Ref ref) async {
ref.watch(authStateProvider);
final client = Supabase.instance.client;
final user = client.auth.currentUser;
if (user == null) return;
late final RealtimeChannel channel;
void invalidate() {
try {
ref.invalidate(currentEntitlementsProvider);
} catch (e) {
dev.log(
'Failed to invalidate entitlement cache: $e',
name: 'EntitlementInvalidationWatcher',
);
}
}
channel = client
.channel('public:entitlement-invalidation:${user.id}')
.onPostgresChanges(
event: PostgresChangeEvent.all,
schema: 'public',
table: 'admin_grants',
callback: (_) => invalidate(),
)
.onPostgresChanges(
event: PostgresChangeEvent.all,
schema: 'public',
table: 'subscriptions',
callback: (_) => invalidate(),
);
channel.subscribe();
ref.onDispose(() {
unawaited(client.removeChannel(channel));
});
}