Real Time Presence Indicators With Firebase Or Supabase
Jan 28, 2026



Summary
Summary
Summary
Summary
This tutorial shows patterns for implementing real-time presence in Flutter mobile development: Firebase Realtime Database uses /.info/connected and onDisconnect for atomic offline writes; Supabase uses a presence table, realtime subscriptions, client heartbeat, and server-side cleanup. Also covers lifecycle hooks, multiple devices, security rules, and debouncing UI updates.
This tutorial shows patterns for implementing real-time presence in Flutter mobile development: Firebase Realtime Database uses /.info/connected and onDisconnect for atomic offline writes; Supabase uses a presence table, realtime subscriptions, client heartbeat, and server-side cleanup. Also covers lifecycle hooks, multiple devices, security rules, and debouncing UI updates.
This tutorial shows patterns for implementing real-time presence in Flutter mobile development: Firebase Realtime Database uses /.info/connected and onDisconnect for atomic offline writes; Supabase uses a presence table, realtime subscriptions, client heartbeat, and server-side cleanup. Also covers lifecycle hooks, multiple devices, security rules, and debouncing UI updates.
This tutorial shows patterns for implementing real-time presence in Flutter mobile development: Firebase Realtime Database uses /.info/connected and onDisconnect for atomic offline writes; Supabase uses a presence table, realtime subscriptions, client heartbeat, and server-side cleanup. Also covers lifecycle hooks, multiple devices, security rules, and debouncing UI updates.
Key insights:
Key insights:
Key insights:
Key insights:
Implementing Presence With Firebase Realtime Database: Use /.info/connected and onDisconnect() to atomically set online/offline state and last_seen.
Implementing Presence With Supabase (Postgres + Realtime): Upsert presence rows, use realtime subscriptions, run a heartbeat, and schedule server cleanup for stale entries.
Client Considerations And Best Practices: Track device-level presence, use app lifecycle hooks, debounce UI updates, and limit subscription scope.
Security And Server Cleanup Strategies: Apply Firebase rules or Supabase RLS to restrict writes and use server cron/edge functions to clear stale connections.
Operational Tradeoffs: Firebase offers immediate server-triggered disconnect handling; Supabase favors portability with heartbeat patterns and eventual cleanup.
Introduction
Real-time presence indicators are essential in modern mobile apps to show which users or devices are online, typing, or available. In Flutter mobile development you have two practical backends: Firebase (Realtime Database or Firestore with helpers) and Supabase (Postgres + Realtime). This tutorial explains reliable patterns for presence, code examples for both platforms, and operational considerations like disconnect handling, security, and cleanup.
Implementing Presence With Firebase Realtime Database
Firebase Realtime Database provides first-class primitives for presence. Use the special /.info/connected node to detect connection state and the onDisconnect() handler to atomically set a user offline when the socket closes unexpectedly. The canonical pattern is:
Maintain a per-user presence node (e.g., /status/{uid}).
When the client connects, write {state: "online", last_seen: SERVER_TIMESTAMP}.
Register onDisconnect() to set {state: "offline", last_seen: SERVER_TIMESTAMP}.
Optionally write a smaller per-device connection record if users connect from multiple devices.
This approach minimizes race conditions because onDisconnect is executed by the server when the TCP/WebSocket connection is lost.
Example (firebase_database package):
final statusRef = FirebaseDatabase.instance.ref('status/$uid'); FirebaseDatabase.instance.ref('.info/connected').onValue.listen((snap) async { final connected = snap.snapshot.value as bool? ?? false; if (connected) { await statusRef.onDisconnect().set({'state': 'offline', 'last_seen': ServerValue.timestamp}); await statusRef.set({'state': 'online', 'last_seen': ServerValue.timestamp}); } });
Firestore does not provide onDisconnect; for Firestore use Cloud Functions triggered on connection events or maintain RTDB for presence alongside Firestore.
Implementing Presence With Supabase (Postgres + Realtime)
Supabase does not provide a built-in onDisconnect primitive for Flutter clients in the same way RTDB does, but you can build robust presence with a presence table and realtime subscriptions:
Create a presence table: (id, user_id, device_id, status, last_seen, connection_id).
On client connect, upsert a row with a generated connection_id and status='online'.
Subscribe to changes on the presence table via Supabase Realtime so other clients get updates.
Implement a heartbeat: the client periodically updates last_seen. If a client disconnects uncleanly, the row remains until a server-side cleanup marks it offline after a stale threshold.
Server cleanup can be a scheduled function (cron) that sets status='offline' for entries with last_seen older than N seconds.
Client heartbeat example (supabase_flutter):
final conn = Uuid().v4(); await supabase.from('presence').upsert({'user_id': uid, 'connection_id': conn, 'status': 'online', 'last_seen': DateTime.now().toUtc().toIso8601String()}); Timer.periodic(Duration(seconds: 20), (_) { supabase.from('presence').update({'last_seen': DateTime.now().toUtc().toIso8601String()}).eq('connection_id', conn); });
This pattern trades immediate cleanup for portability and resilience; realtime subscriptions give other clients near-instant updates while cron ensures eventual consistency.
Client Considerations And Best Practices
Multiple Devices: Track device-level presence with device IDs. Aggregate per-user online state on the client by counting active devices.
App Lifecycle: Hook into WidgetsBindingObserver (AppLifecycleState) to set offline on pause and online on resume, but still rely on server-side disconnect/heartbeat to handle crashes.
Debounce UI Updates: Presence churn (connect/disconnect flaps) can cause noisy UI. Debounce rapid state changes or treat brief disconnects as still-online for a small grace window.
Efficient Subscriptions: Subscribe only to presence nodes necessary for the current UI (e.g., conversation participants), and reuse subscriptions when navigating.
Security: Enforce rules so clients can only write their own presence rows. In Firebase use security rules keyed by auth.uid; in Supabase use Row Level Security (RLS) policies tied to auth.uid.
Security And Server Cleanup Strategies
Firebase: Use database rules to ensure users only change their own status entries. onDisconnect reduces the attack surface because the server triggers the final write.
Supabase: Use RLS to prevent arbitrary presence updates. Create a service-side scheduled job (Edge Function or server cron) that sweeps stale rows and marks them offline after a configurable timeout (for example, 60–90 seconds without heartbeat).
Cloud Functions: For Firestore-only apps, consider a Cloud Function that listens for client open/close signals (via a lightweight RTDB or websocket gateway) and reconciles presence in Firestore.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Real-time presence in Flutter mobile development requires both reliable connection handling and careful server-side policies. Firebase Realtime Database gives an atomic onDisconnect primitive that simplifies correct offline handling. Supabase requires a heartbeat-plus-cleanup pattern but works well with Realtime subscriptions and RLS. Use device-level records, lifecycle hooks, debouncing, and server cleanup to build accurate, efficient presence indicators that scale across users and devices.
Introduction
Real-time presence indicators are essential in modern mobile apps to show which users or devices are online, typing, or available. In Flutter mobile development you have two practical backends: Firebase (Realtime Database or Firestore with helpers) and Supabase (Postgres + Realtime). This tutorial explains reliable patterns for presence, code examples for both platforms, and operational considerations like disconnect handling, security, and cleanup.
Implementing Presence With Firebase Realtime Database
Firebase Realtime Database provides first-class primitives for presence. Use the special /.info/connected node to detect connection state and the onDisconnect() handler to atomically set a user offline when the socket closes unexpectedly. The canonical pattern is:
Maintain a per-user presence node (e.g., /status/{uid}).
When the client connects, write {state: "online", last_seen: SERVER_TIMESTAMP}.
Register onDisconnect() to set {state: "offline", last_seen: SERVER_TIMESTAMP}.
Optionally write a smaller per-device connection record if users connect from multiple devices.
This approach minimizes race conditions because onDisconnect is executed by the server when the TCP/WebSocket connection is lost.
Example (firebase_database package):
final statusRef = FirebaseDatabase.instance.ref('status/$uid'); FirebaseDatabase.instance.ref('.info/connected').onValue.listen((snap) async { final connected = snap.snapshot.value as bool? ?? false; if (connected) { await statusRef.onDisconnect().set({'state': 'offline', 'last_seen': ServerValue.timestamp}); await statusRef.set({'state': 'online', 'last_seen': ServerValue.timestamp}); } });
Firestore does not provide onDisconnect; for Firestore use Cloud Functions triggered on connection events or maintain RTDB for presence alongside Firestore.
Implementing Presence With Supabase (Postgres + Realtime)
Supabase does not provide a built-in onDisconnect primitive for Flutter clients in the same way RTDB does, but you can build robust presence with a presence table and realtime subscriptions:
Create a presence table: (id, user_id, device_id, status, last_seen, connection_id).
On client connect, upsert a row with a generated connection_id and status='online'.
Subscribe to changes on the presence table via Supabase Realtime so other clients get updates.
Implement a heartbeat: the client periodically updates last_seen. If a client disconnects uncleanly, the row remains until a server-side cleanup marks it offline after a stale threshold.
Server cleanup can be a scheduled function (cron) that sets status='offline' for entries with last_seen older than N seconds.
Client heartbeat example (supabase_flutter):
final conn = Uuid().v4(); await supabase.from('presence').upsert({'user_id': uid, 'connection_id': conn, 'status': 'online', 'last_seen': DateTime.now().toUtc().toIso8601String()}); Timer.periodic(Duration(seconds: 20), (_) { supabase.from('presence').update({'last_seen': DateTime.now().toUtc().toIso8601String()}).eq('connection_id', conn); });
This pattern trades immediate cleanup for portability and resilience; realtime subscriptions give other clients near-instant updates while cron ensures eventual consistency.
Client Considerations And Best Practices
Multiple Devices: Track device-level presence with device IDs. Aggregate per-user online state on the client by counting active devices.
App Lifecycle: Hook into WidgetsBindingObserver (AppLifecycleState) to set offline on pause and online on resume, but still rely on server-side disconnect/heartbeat to handle crashes.
Debounce UI Updates: Presence churn (connect/disconnect flaps) can cause noisy UI. Debounce rapid state changes or treat brief disconnects as still-online for a small grace window.
Efficient Subscriptions: Subscribe only to presence nodes necessary for the current UI (e.g., conversation participants), and reuse subscriptions when navigating.
Security: Enforce rules so clients can only write their own presence rows. In Firebase use security rules keyed by auth.uid; in Supabase use Row Level Security (RLS) policies tied to auth.uid.
Security And Server Cleanup Strategies
Firebase: Use database rules to ensure users only change their own status entries. onDisconnect reduces the attack surface because the server triggers the final write.
Supabase: Use RLS to prevent arbitrary presence updates. Create a service-side scheduled job (Edge Function or server cron) that sweeps stale rows and marks them offline after a configurable timeout (for example, 60–90 seconds without heartbeat).
Cloud Functions: For Firestore-only apps, consider a Cloud Function that listens for client open/close signals (via a lightweight RTDB or websocket gateway) and reconciles presence in Firestore.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Real-time presence in Flutter mobile development requires both reliable connection handling and careful server-side policies. Firebase Realtime Database gives an atomic onDisconnect primitive that simplifies correct offline handling. Supabase requires a heartbeat-plus-cleanup pattern but works well with Realtime subscriptions and RLS. Use device-level records, lifecycle hooks, debouncing, and server cleanup to build accurate, efficient presence indicators that scale across users and devices.
Build Flutter Apps Faster with Vibe Studio
Vibe Studio is your AI-powered Flutter development companion. Skip boilerplate, build in real-time, and deploy without hassle. Start creating apps at lightning speed with zero setup.
Other Insights






















