←Writing
Mobile & Flutter•January 2026•5 min read

Architecting Offline-First Streak Engines with Flutter Riverpod 2.x and Supabase

How we built deterministic streak calculation logic, optimistic local updates, and conflict-free cloud replication in HabitFlow without latency penalties.

Ismail Ismail

Ismail Ismail

Software Engineering Student · AI-Native Builder (Kigali, Rwanda)

The Offline-First Paradox in Habit Tracking

In productivity apps, habit streak tracking requires zero latency. If a user marks a habit complete in airplane mode or in low-connectivity areas, the UI must immediately increment the streak, play celebration haptics, and resolve conflicts once the device reconnects.

terminal
1[ User Tap ] ──► ( Optimistic State Mutator ) ──► [ Instant UI Update (0ms) ]
2 │
3 ▼
4 [ Local SQLite / Hive Cache ]
5 │
6 ▼ (Background Sync)
7 [ Supabase Realtime Postgres ]

1. State Modeling with Riverpod 2.x Notifiers

We modeled each habit entity as an immutable record. Using Riverpod 2.x `AsyncNotifier`, we separate UI state rendering from backend sync tasks:

dart
1@riverpod
2class HabitStreakNotifier extends _$HabitStreakNotifier {
3 @override
4 Future<List<HabitRecord>> build() async {
5 // 1. Fetch instantly from local disk cache
6 final localData = await ref.read(localCacheProvider).getHabits();
7
8 // 2. Trigger asynchronous background sync with Supabase
9 _syncWithCloud();
10
11 return localData;

Future<void> markCompleted(String habitId) async { final previousState = state; // Optimistic UI mutation: Increment streak immediately state = state.whenData((habits) => habits.map((h) { if (h.id == habitId) return h.incrementForToday(); return h; }).toList());

try { await ref.read(supabaseClientProvider) .from('habit_logs') .upsert({'habit_id': habitId, 'timestamp': DateTime.now().toIso8601String()}); } catch (err) { // Revert state if sync fails fatally state = previousState; } } } ```


2. Timezone & Leap-Day Determinism

A common flaw in habit apps is timezone-drift errors when calculating whether a streak is broken. We resolved this by computing streak intervals in UTC ISO day-strings (`YYYY-MM-DD`) paired with user local timezone offsets stored in PostgreSQL.

💡 Key Architectural TakeawayResult: HabitFlow achieved 100% offline functionality and zero UI freezing on completion taps across 500+ active beta test logs.
#Flutter#Dart#Riverpod 2.x#Supabase#Offline-First#PostgreSQL