SnapTask
Mobile-first task management platform with real-time messaging, push notifications, and cross-platform support.
2017 was a weird time for mobile development. React Native was still the new kid, and half the internet was convinced it was a fad. "Just learn Swift," they said. "Cross-platform never works," they said.
I didn't listen. And honestly? It was a gamble that paid off.
The Origin Story
SnapTask started because my team was using three different apps to manage work: Slack for chat, Trello for tasks, and email for... I don't know, suffering? The context switching was killing us. Every time I needed to check on a task, I'd lose 10 minutes falling down a Slack rabbit hole.
The pitch was simple: What if your tasks and your team chat lived in the same place?
React Native Before It Was Cool
This was React Native 0.44. There was no Expo. There was no Flipper. There was just you, the Metro bundler, and your faith in the JavaScript gods.
I remember spending three days debugging an issue where the app would crash on Android but only on Samsung devices. Only Samsungs. The culprit? A font that Samsung's version of Android didn't like. The fix? A different font. That's it. Three days.
But here's the thing — we shipped ONE codebase to both app stores. While our competitors were maintaining two separate apps with two separate teams, we were iterating twice as fast with half the people.
The Architecture That Worked
The backend was Ruby on Rails because in 2017, Rails was still the "get shit done" framework. (It still is, but people are weird about it now.)
- •ActionCable ...
Built With
Impact
Platform
iOS/Android
Features
Real-time
Under the Hood
A peek at the implementation — the kind of code that powers SnapTask.
1// The offline-first sync engine that saved us countless support tickets
2class SyncEngine {
3 constructor() {
4 this.queue = new PersistentQueue('pending-actions');
5 this.isOnline = NetInfo.isConnected;
6
7 // Attempt sync whenever we come back online
8 NetInfo.addEventListener(state => {
9 if (state.isConnected && !this.isOnline) {
10 this.processQueue();
11 }
12 this.isOnline = state.isConnected;
13 });
14 }
15
16 async performAction(action) {
17 // Optimistically apply the action locally
18 await this.applyLocally(action);
19
20 // Queue for server sync
21 await this.queue.push({
22 ...action,
23 timestamp: Date.now(),
24 retries: 0
25 });
26
27 // Try to sync immediately if online
28 if (this.isOnline) {
29 this.processQueue();
30 }
31 }
32
33 async processQueue() {
34 const pending = await this.queue.peek();
35 if (!pending) return;
36
37 try {
38 await api.sync(pending);
39 await this.queue.pop();
40 this.processQueue(); // Process next
41 } catch (err) {
42 if (pending.retries < 3) {
43 pending.retries++;
44 // Exponential backoff
45 setTimeout(() => this.processQueue(), 1000 * pending.retries);
46 }
47 }
48 }
49}