DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter Web + Supabase Realtime: From Channel Subscriptions to Optimistic Updates

Flutter Web + Supabase Realtime: From Channel Subscriptions to Optimistic Updates

Supabase Realtime is easy to start with and easy to get wrong in production. Here are the patterns that worked in my Flutter Web project, covering channel management, optimistic updates, and the gotchas that burned me.

Basic: Channel Subscription

// lib/services/realtime_service.dart
class RealtimeService {
  final _supabase = Supabase.instance.client;
  RealtimeChannel? _channel;

  void subscribeMemos(String userId, void Function(List<Memo>) onUpdate) {
    _channel = _supabase
      .channel('memos:$userId')
      .onPostgresChanges(
        event: PostgresChangeEvent.all,
        schema: 'public',
        table: 'memos',
        filter: PostgresChangeFilter(
          type: PostgresChangeFilterType.eq,
          column: 'user_id',
          value: userId,
        ),
        callback: (payload) {
          onUpdate(_buildMemoList(payload));
        },
      )
      .subscribe();
  }

  void dispose() {
    _channel?.unsubscribe();
    _supabase.removeAllChannels();
  }
}
Enter fullscreen mode Exit fullscreen mode

Critical: forgetting dispose() leaks the WebSocket connection. Always call it in your StatefulWidget's dispose() method.

Optimistic Updates

Don't wait for the realtime event — update the UI immediately and roll back on failure:

Future<void> toggleReaction(String memoId, String reactionType) async {
  // 1. Optimistic update (immediate)
  setState(() {
    _reactions[memoId] = [...?_reactions[memoId], reactionType];
  });

  try {
    // 2. Server call
    await _supabase.functions.invoke('core-hub', body: {
      'action': 'memo.react.toggle',
      'params': {'memo_id': memoId, 'reaction_type': reactionType},
    });
    // 3. Realtime delivers confirmed value → setState again with real data
  } catch (e) {
    // 4. Rollback on failure
    setState(() {
      _reactions[memoId]?.remove(reactionType);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Monitoring Connection State

_channel = _supabase.channel('memos:$userId')
  ..onPostgresChanges(/* ... */)
  ..onSubscribe((status, error) {
    if (status == RealtimeSubscribeStatus.subscribed) {
      debugPrint('realtime: connected');
    } else if (status == RealtimeSubscribeStatus.timedOut) {
      // Flutter Web tabs going to background disconnect frequently
      _reconnect();
    }
  })
  ..subscribe();
Enter fullscreen mode Exit fullscreen mode

Flutter Web WebSocket connections drop when the tab goes to the background. Detect timeouts via onSubscribe and reconnect proactively.

Row Level Security Integration

-- RLS policy on memos table
CREATE POLICY "users can see own memos"
ON memos FOR SELECT
USING (auth.uid() = user_id);
Enter fullscreen mode Exit fullscreen mode

RLS policies apply to Realtime subscriptions automatically. Other users' memos never reach the channel.

Performance: Debouncing High-Frequency Updates

Timer? _debounce;

void onTyping(String text) {
  _debounce?.cancel();
  _debounce = Timer(const Duration(milliseconds: 300), () async {
    await _supabase.from('typing_status').upsert({
      'user_id': userId,
      'is_typing': text.isNotEmpty,
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

300ms debounce throttles DB writes for typing indicators. Without this, each keystroke hits the database.

Common Pitfalls

Problem Cause Fix
Channel subscribed multiple times subscribe() called repeatedly Call dispose() before re-subscribing
RLS blocks realtime events anon key + wrong policy Check auth state + verify policy
Flutter Web disconnects Tab goes inactive Reconnect in timeout callback
memo_reactions 404 Stale EF still referenced Verify migration to core-hub is complete

Summary

Flutter + Supabase Realtime reliability comes down to connection lifecycle management. Tie subscribe/unsubscribe to StatefulWidget lifecycle, use optimistic updates for snappy UX, secure with RLS — those three principles cover the vast majority of production issues.

Top comments (0)