Real-Time Collaborative Editing in Mobile Apps: A Developer's Guide
Why Collaborative Editing is Hard in Mobile Environments
Note: When two engineers simultaneously edit the same document, mobile environments introduce three scenarios: unstable networks with packet loss (up to 30% on weak signal), background synchronization after offline mode, and native keyboard with composition events – input latency of 300+ ms. We encountered this in a corporate editor for 500+ users, where we had to balance ACID guarantees with UI responsiveness. Our team implements such solutions turnkey using proven algorithms. With 5+ years of experience and 10+ projects, we guarantee stable synchronization even with network delays up to 2 seconds.
Choosing a Synchronization Algorithm: OT vs CRDT
| Characteristic | OT (Operational Transform) | CRDT (Y.js, Automerge) |
|---|---|---|
| Coordinator | Server required | Optional (works offline) |
| Offline mode | Buffering with subsequent merge | Native support ideal |
| Performance | Low latency (<30ms) with stable network | Efficient for async synchronization (50-100ms) |
| Implementation complexity | Medium (server-side transformation) | Low (no central logic) |
| Memory usage | ~10MB per 1000 operations | ~5MB per 1000 operations |
OT is proven in Google Docs and Apache Wave – server coordinates operations, eliminating conflicts. CRDT (Y.js – de facto standard) does not require a server: merge works locally. For a mobile corporate editor we choose OT; for offline-first editing, CRDT is 2x faster in sync time. See Wikipedia: Operational Transformation and Yjs Documentation for details.
How Y.js Solves Collision Issues in React Native
yjs is pure JavaScript, works in React Native without modifications. Typical setup:
import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; const ydoc = new Y.Doc(); const provider = new WebsocketProvider('wss://your-server.com/sync', 'doc-room-id', ydoc); const ytext = ydoc.getText('document'); YText is a CRDT type with formatting support (bold, italic, headers). Integration with web editors via WebView gives a quick MVP. For native UX, a custom TextInput with manual synchronization via Y.js operations. On iOS: UITextViewDelegate, on Android: TextWatcher. For performance, use line height 18sp and debounce 200ms.
How to Implement Y.js in React Native
To implement Y.js in a React Native collaborative editor, follow these steps:
- Install dependencies:
npm install yjs y-websocket. - Create a Y.Doc and connect to a WebSocket provider with a room ID.
- Obtain a Y.Text type for the document content.
- Bind the Y.Text to a native TextInput using a custom bridge for iOS/Android.
- Implement cursor awareness using the Awareness protocol.
- Persist document state to SQLite on changes using
Y.encodeStateAsUpdate. - Reconnect and sync on app resume.
Cursors and Awareness
Y.js Awareness Protocol distributes ephemeral data (cursors, presence) among participants. Not stored in the document:
provider.awareness.setLocalState({ user: { name: 'Ivan', color: '#3B82F6' }, cursor: { anchor: 45, focus: 45 } }); provider.awareness.on('change', () => { const states = Array.from(provider.awareness.getStates().values()); // update other users' cursor positions }); Displaying other cursors in native TextInput is non-trivial. Need to convert character position to pixel coordinates using UITextView.caretRect(for:) on iOS and Layout.getDesiredWidth() on Android. For this, we use Y.js Awareness and custom calculations – pixel accuracy at 60fps.
How Are Formatting Conflicts Resolved?
Y.js YText formatting works via Delta operations with attributes. Concurrent formatting (one user makes bold, another italic on overlap) – both attributes apply. For semantically incompatible operations (H1 vs H2), Y.js selects based on clientId, but UI should warn or allow manual resolve. In one project, we added a modal dialog – users resolve conflict in 2 seconds.
Persistence and History
Y.js document serializes to Uint8Array via Y.encodeStateAsUpdate(). On mobile, store in SQLite using react-native-sqlite-storage. On open: load saved state, apply via Y.applyUpdate(), then connect to WebSocket for diff. Server uses y-leveldb or y-redis for persistence. Optimization: snapshots every 100 operations – load time <500ms.
Implementation Technical Details
For the server side, a minimalistic Node.js server from the y-websocket package suffices. In production, add persistence, auth, and room access control.
Comparison: Native vs WebView
| Criterion | Native TextInput + Y.js | WebView + Quill.js |
|---|---|---|
| Performance | High (60fps) – 3x better than WebView | Medium (30-45fps at 500 chars) |
| Complexity | High (manual sync) | Low (ready library) |
| Formatting support | Limited (custom buttons) | Full (toolbar) |
| MVP timeline | 12–16 weeks | 8–10 weeks |
For mobile app synchronization, native TextInput with Y.js offers 3x better performance compared to WebView, essential for smooth cursor rendering.
What Is Included in the Work
- Analysis: Scenario evaluation and algorithm selection (OT/CRDT).
- Architecture: Design of synchronization using WebSocket sync.
- Implementation: Text editor with native rendering and Y.js or OT server.
- Cursors: Development of cursor awareness and display.
- Testing: Real device testing with poor network emulation (30% packet loss).
- Deliverables:
- Documentation: user guides, API references, and architecture diagrams.
- Access: source code repository and server credentials.
- Training: team workshops on maintenance and scaling.
- Support: 1 month post-deployment including bug fixes.
Typical MVP costs range from $20,000 to $40,000, with potential savings of up to 30% compared to full custom development. Project costs start at $20,000 for an MVP, and we offer a 10% discount for early commitments.
Get a consultation – we will assess your project. Write to us – we will show code examples.







