supabase_realtime_widgets 0.1.0
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.
// A minimal example app for supabase_realtime_widgets.
//
// Before running:
// 1. Create a Supabase project and a `todos` table:
// id bigint primary key generated always as identity
// title text not null
// is_done boolean not null default false
// created_at timestamptz not null default now()
// 2. Enable Realtime on the `todos` table.
// 3. Drop your project URL + anon key into the constants below.
// 4. `flutter run` from this example/ directory.
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_realtime_widgets/supabase_realtime_widgets.dart';
const String supabaseUrl = 'https://YOUR_PROJECT.supabase.co';
const String supabaseAnonKey = 'YOUR_ANON_KEY';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey);
runApp(const TodoApp());
}
class Todo {
const Todo({required this.id, required this.title, required this.isDone});
factory Todo.fromJson(Map<String, dynamic> json) => Todo(
id: json['id'] as int,
title: json['title'] as String,
isDone: (json['is_done'] as bool?) ?? false,
);
final int id;
final String title;
final bool isDone;
}
class TodoApp extends StatelessWidget {
const TodoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Realtime Todos',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.teal),
home: const TodoListScreen(),
);
}
}
class TodoListScreen extends StatefulWidget {
const TodoListScreen({super.key});
@override
State<TodoListScreen> createState() => _TodoListScreenState();
}
class _TodoListScreenState extends State<TodoListScreen> {
final TextEditingController _controller = TextEditingController();
Future<void> _addTodo() async {
final text = _controller.text.trim();
if (text.isEmpty) return;
await Supabase.instance.client.from('todos').insert({'title': text});
_controller.clear();
}
Future<void> _toggle(Todo todo) async {
await Supabase.instance.client
.from('todos')
.update({'is_done': !todo.isDone}).eq('id', todo.id);
}
Future<void> _delete(Todo todo) async {
await Supabase.instance.client.from('todos').delete().eq('id', todo.id);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Realtime Todos')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'What needs doing?',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 8),
FilledButton(onPressed: _addTodo, child: const Text('Add')),
],
),
),
Expanded(
child: RealtimeListBuilder<Todo>(
client: Supabase.instance.client,
table: 'todos',
fromJson: Todo.fromJson,
getId: (t) => t.id,
orderBy: 'created_at',
ascending: false,
loadingBuilder: (_) =>
const Center(child: CircularProgressIndicator()),
errorBuilder: (_, error) =>
Center(child: Text('Error: $error')),
builder: (context, todos) {
if (todos.isEmpty) {
return const Center(child: Text('No todos yet.'));
}
return ListView.separated(
itemCount: todos.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, i) {
final todo = todos[i];
return ListTile(
leading: Checkbox(
value: todo.isDone,
onChanged: (_) => _toggle(todo),
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isDone
? TextDecoration.lineThrough
: null,
),
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _delete(todo),
),
);
},
);
},
),
),
],
),
);
}
}