supabase_realtime_widgets 0.1.0 copy "supabase_realtime_widgets: ^0.1.0" to clipboard
supabase_realtime_widgets: ^0.1.0 copied to clipboard

A small set of Flutter widgets that bind directly to Supabase Realtime channels — list, value, and presence builders with automatic subscription lifecycle.

supabase_realtime_widgets #

A small set of Flutter widgets that bind directly to Supabase Realtime channels — list, value, and presence builders that own the subscription lifecycle so you don't have to.

pub package License: MIT

Why #

supabase_flutter already exposes Realtime via streams and channels, but most apps end up writing the same boilerplate over and over: fetch the initial page, subscribe to changes, merge INSERT/UPDATE/DELETE events into a local list, and tear it all down on dispose(). This package collapses that boilerplate into three widgets:

  • RealtimeListBuilder<T> — a live list, like StreamBuilder but with the merge logic baked in.
  • RealtimeValueBuilder<T> — a single live row, watched by primary key.
  • PresenceBuilder — a live roster of who else is on a channel.

It's intentionally tiny: no state management opinions, no extra abstractions, no lock-in. You provide the model, the fromJson, and the builder. The widgets do the rest.

Install #

dependencies:
  supabase_flutter: ^2.5.0
  supabase_realtime_widgets: ^0.1.0

RealtimeListBuilder #

import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_realtime_widgets/supabase_realtime_widgets.dart';

class Todo {
  Todo({required this.id, required this.title});
  factory Todo.fromJson(Map<String, dynamic> json) =>
      Todo(id: json['id'] as int, title: json['title'] as String);
  final int id;
  final String title;
}

RealtimeListBuilder<Todo>(
  client: Supabase.instance.client,
  table: 'todos',
  fromJson: Todo.fromJson,
  getId: (t) => t.id,
  orderBy: 'created_at',
  ascending: false,
  builder: (context, todos) => ListView(
    children: [for (final t in todos) ListTile(title: Text(t.title))],
  ),
)

What you get:

  • The initial fetch runs once, ordered and limited per your config.
  • Inserts append. Updates replace by primary key. Deletes remove. The original list is never mutated.
  • dispose() removes the channel automatically.
  • An UPDATE for a row that isn't in the local list (a missed event after a reconnect) appends instead of dropping silently — so the UI converges back to the upstream state.

RealtimeValueBuilder #

For a single row that needs to stay live (a header, a detail screen, a profile card):

RealtimeValueBuilder<Profile>(
  client: Supabase.instance.client,
  table: 'profiles',
  id: userId,
  fromJson: Profile.fromJson,
  builder: (context, profile) {
    if (profile == null) return const Text('Profile deleted.');
    return Text('Hi, ${profile.displayName}');
  },
)

If the row is deleted upstream, the builder receives null. No exceptions, no manual existence checks.

PresenceBuilder #

For "who's here right now" — collaborative cursors, viewer counts, room rosters:

PresenceBuilder(
  client: Supabase.instance.client,
  channelName: 'room:42',
  self: {'user_id': currentUserId, 'name': currentUserName},
  builder: (context, members) {
    return Wrap(
      spacing: 8,
      children: [
        for (final m in members)
          Chip(label: Text(m['name']?.toString() ?? '?')),
      ],
    );
  },
)

The current user is track'd on subscribe and untrack'd on dispose. Joins, leaves, and the initial sync all rebuild the widget.

Testable list-merge logic #

The hard part of any realtime list — applying a Postgres change payload to an in-memory list — lives in a pure-Dart ChangeMerger<T> class that you can unit test without spinning up a Supabase client:

final merger = ChangeMerger<Todo>(
  fromJson: Todo.fromJson,
  getId: (t) => t.id,
);

final next = merger.apply(currentList, payload);

See test/change_merger_test.dart for the full set of cases.

Example app #

A complete live-todo example lives in example/. It needs a Supabase project and a 4-column todos table — the README in that directory walks you through it in three steps.

Design notes #

  • No state management opinions. These widgets are StatefulWidgets that own their own subscription. Drop them inside Riverpod, BLoC, GetX, or nothing at all.
  • Generic over T. You write the model and the converter; the package never inspects your row shape.
  • Channel names are deterministic. Each widget uses a stable channel name (schema:table:rt-list, etc.) so multiple widgets watching the same table share infrastructure where possible.
  • No reconnect handling beyond what supabase_flutter provides. Supabase Realtime handles socket-level reconnects; this package only handles event merging.

Status #

0.1.0 — initial release. APIs are intentionally small and may evolve. Open an issue if you hit something the three widgets can't express.

License #

MIT. See LICENSE.

0
likes
160
points
8
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A small set of Flutter widgets that bind directly to Supabase Realtime channels — list, value, and presence builders with automatic subscription lifecycle.

Repository (GitHub)
View/report issues

Topics

#supabase #realtime #widget #postgres #presence

License

MIT (license)

Dependencies

flutter, supabase_flutter

More

Packages that depend on supabase_realtime_widgets