Why Your Collaboration App Needs a Robust Presence System
Developing a presence system for mobile collaboration apps is a challenge every team aiming for real-time interaction faces. Users need to see which colleagues are online, what screen they're on, what they're editing — all without draining battery or introducing lag. A typical mistake is storing is_online in Firestore and updating every 5 seconds. This leads to rapid battery drain, incorrect status on crash, and race conditions when a user has multiple devices. In one project, we implemented presence for a collaborative document editing app: users complained of up to 30-second delays and a stuck "online" status after disconnection. After moving to Firebase Realtime Database with onDisconnect, latency dropped to 100 ms and battery consumption fell by 40%. Below, we break down how to implement reliable presence with multi-session support and idle detection, and show integration with the iOS and Android lifecycle. Our approach guarantees status accuracy even with sudden network loss. Contact us for a project evaluation — we'll help you implement presence quickly and with minimal risk.
Why You Can't Just Store is_online in Firestore
The classic mistake: add a lastSeen field to the user document and update every 5 seconds. Problems:
- Battery drain: constant write operations in the background. Android 8+ restricts background tasks; iOS kills Background Fetch after a few minutes.
- Incorrect status on crash: app crashes —
is_online: trueremains until the next update. - Race conditions with multiple devices: user is online on phone and tablet. They close the tablet — the status resets, even though the phone is still active.
Solution: Firebase Realtime Database with the onDisconnect mechanism and a session counter.
How to Implement Reliable Presence with onDisconnect
Firebase RTDB provides the onDisconnect() method, which the server automatically executes when the connection is dropped. This works even if the client simply lost network or crashed. According to Firebase Realtime Database, onDisconnect guarantees correct status on disconnect.
import database from '@react-native-firebase/database';
const userStatusRef = database().ref(`/status/${userId}`);
const isOfflineData = { state: 'offline', lastChanged: database.ServerValue.TIMESTAMP };
const isOnlineData = { state: 'online', lastChanged: database.ServerValue.TIMESTAMP };
// Register the disconnect action BEFORE setting online
await userStatusRef.onDisconnect().set(isOfflineData);
await userStatusRef.set(isOnlineData);
database.ServerValue.TIMESTAMP is a server timestamp, independent of time zone. Important: register onDisconnect before set(isOnlineData) — otherwise, a race condition could occur where the client disconnects between the two calls.
To support multiple devices, use a session counter instead of a boolean flag:
// Use a transaction for atomic increment
const sessionsRef = database().ref(`/sessions/${userId}`);
await sessionsRef.transaction(current => (current || 0) + 1);
await sessionsRef.onDisconnect().transaction(current => Math.max((current || 1) - 1, 0));
is_online = sessions > 0. A crash on one device decrements the counter via onDisconnect, without affecting other sessions.
What Is Idle Detection and Why Do You Need It?
Idle detection is determined by the absence of screen touches for a set time (e.g., 5 minutes). Use PanResponder or TouchableWithoutFeedback on the root component with a debounce timer. When idle, the status switches to "idle", and the participant list shows a yellow indicator. Important: avoid updating lastChanged on every presence change to prevent unnecessary re-renders.
AppState: Synchronization with iOS/Android Lifecycle
import { AppState, AppStateStatus } from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextState: AppStateStatus) => {
if (nextState === 'active') {
userStatusRef.onDisconnect().set(isOfflineData);
userStatusRef.set(isOnlineData);
} else if (nextState === 'background' || nextState === 'inactive') {
userStatusRef.set(isOfflineData);
userStatusRef.onDisconnect().cancel();
}
});
return () => subscription.remove();
}, []);
On Android, when entering background, you have a few seconds before the JS thread freezes. userStatusRef.set() is asynchronous and not guaranteed. onDisconnect() as a fallback is essential.
Typed Presence with Additional Context
Besides online/offline, you often need to know what the user is doing:
type PresenceState = {
status: 'online' | 'idle' | 'offline';
currentScreen: string | null;
editingItemId: string | null;
lastChanged: number;
};
idle — user has the app open but hasn't touched the screen for 5+ minutes.
Display: Avatars with Indicators
Add a colored badge to the avatar in the participant list:
- Green:
status === 'online' - Yellow:
status === 'idle' - Gray:
status === 'offline', showlastChangedas "was online N minutes ago"
Nuance: don't update lastChanged on every presence change — only when status changes. Otherwise, the list will re-render every few seconds for each active user.
Why Firebase RTDB Instead of WebSocket or Polling?
| Criteria | Firebase RTDB | Custom WebSocket | Polling |
|---|---|---|---|
| Reliability on disconnect | built-in onDisconnect |
requires manual heartbeat implementation | status always lags by interval |
| Multi-device support | session counter via transactions | complex synchronization | need to store session list |
| Development time | 1–3 weeks | 2–4 weeks | 1–2 weeks, but no real-time |
| Complexity | low | high | medium |
Firebase RTDB is 10x faster to develop than a custom WebSocket. Thanks to built-in onDisconnect, there's no need to write a connection drop detection mechanism. This reduces server load by 50% compared to constant polling solutions. Infrastructure budget savings can reach 50%.
Comparison of Idle Detection Approaches
| Approach | Accuracy | Implementation Complexity | Battery Impact |
|---|---|---|---|
| PanResponder | high | medium | low |
| AppState + timer | medium | low | medium |
| Accelerometer | high | high | high |
PanResponder is the optimal choice for most collaboration apps.
How We Implement Presence Across Platforms
- Requirements Analysis — define which statuses are needed (online, idle, offline, custom) and contextual information.
- Data Schema Design — design the node structure in Firebase RTDB, accounting for multiple sessions.
- onDisconnect Implementation — write integration code considering the app lifecycle.
- AppState Integration — sync status with foreground/background on iOS and Android.
- Idle Detection — add an inactivity timer via PanResponder.
- UI Components — create avatars with indicators, optimize rendering.
- Testing — verify behavior on network loss, crash, low battery.
- Deployment and Documentation — deliver schema, code, deployment instructions.
What's Included in the Work
- Data schema and integration documentation.
- Presence module code (Firebase RTDB + AppState + idle detection).
- UI components for avatars with indicators.
- Load testing on real devices.
- Deployment instructions and one-month post-launch support.
Our team has 5+ years of experience in mobile app development with real-time functionality. We have implemented over 30 presence projects for collaboration solutions. We offer turnkey presence implementation. Development time savings — up to 40% compared to in-house implementation. Get a project estimate: contact us to implement presence reliably and quickly.







