Flutter Supabase
Open-source Firebase alternative for Flutter apps
🚀 What is Supabase?
Supabase is an open-source backend platform providing authentication, database, storage, and real-time subscriptions. It's a complete Firebase alternative that gives you a PostgreSQL database, instant APIs, and powerful features for Flutter apps.
// Initialize Supabase
import 'package:supabase_flutter/supabase_flutter.dart';
await Supabase.initialize(
url: 'YOUR_SUPABASE_URL',
anonKey: 'YOUR_SUPABASE_ANON_KEY',
);
final supabase = Supabase.instance.client;
Key Supabase Features
Authentication
Built-in user authentication with email, social logins, and magic links. Secure user management with JWT tokens, password reset, and email verification out of the box.
await supabase.auth.signUp(
email: '[email protected]',
password: 'password123',
);
Database
PostgreSQL database with instant REST API. Create tables, run queries, and manage data with powerful SQL features and automatic API generation for all tables.
final data = await supabase
.from('users')
.select()
.eq('id', userId);
Storage
File storage for images, videos, and documents. Upload, download, and manage files with built-in CDN, image transformations, and access control policies.
await supabase.storage
.from('avatars')
.upload('user1.jpg', file);
Real-time
Live database changes via WebSocket subscriptions. Listen to inserts, updates, and deletes in real-time, perfect for chat apps, collaborative tools, and live dashboards.
supabase
.from('messages')
.stream(primaryKey: ['id'])
.listen((data) { });
🔹 Setting Up Supabase
Add Supabase to your Flutter project:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
supabase_flutter: ^2.0.0
// main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://your-project.supabase.co',
anonKey: 'your-anon-key',
);
runApp(MyApp());
}
// Access Supabase client anywhere
final supabase = Supabase.instance.client;
Setup steps:
1. Create account at supabase.com
2. Create new project
3. Get URL and anon key from settings
4. Initialize in your Flutter app
🔹 User Authentication
Implement sign up, sign in, and sign out:
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class AuthScreen extends StatefulWidget {
@override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final emailController = TextEditingController();
final passwordController = TextEditingController();
final supabase = Supabase.instance.client;
bool isLoading = false;
Future<void> signUp() async {
setState(() => isLoading = true);
try {
await supabase.auth.signUp(
email: emailController.text,
password: passwordController.text,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Check your email for confirmation!')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() => isLoading = false);
}
}
Future<void> signIn() async {
setState(() => isLoading = true);
try {
await supabase.auth.signInWithPassword(
email: emailController.text,
password: passwordController.text,
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() => isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Authentication')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
),
SizedBox(height: 16),
TextField(
controller: passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 24),
if (isLoading)
CircularProgressIndicator()
else
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: signUp,
child: Text('Sign Up'),
),
ElevatedButton(
onPressed: signIn,
child: Text('Sign In'),
),
],
),
],
),
),
);
}
}
Features:
✓ Email/password authentication
✓ Email verification
✓ Loading states
✓ Error handling
🔹 Database Operations
Perform CRUD operations on your database:
// Create (Insert)
Future<void> createNote(String title, String content) async {
await supabase.from('notes').insert({
'title': title,
'content': content,
'user_id': supabase.auth.currentUser!.id,
});
}
// Read (Select)
Future<List<Map<String, dynamic>>> getNotes() async {
final response = await supabase
.from('notes')
.select()
.eq('user_id', supabase.auth.currentUser!.id)
.order('created_at', ascending: false);
return response as List<Map<String, dynamic>>;
}
// Update
Future<void> updateNote(int id, String title, String content) async {
await supabase.from('notes').update({
'title': title,
'content': content,
}).eq('id', id);
}
// Delete
Future<void> deleteNote(int id) async {
await supabase.from('notes').delete().eq('id', id);
}
// Usage in a widget
class NotesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('My Notes')),
body: FutureBuilder<List<Map<String, dynamic>>>(
future: getNotes(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final notes = snapshot.data!;
return ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) {
final note = notes[index];
return ListTile(
title: Text(note['title']),
subtitle: Text(note['content']),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => deleteNote(note['id']),
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => createNote('New Note', 'Content here'),
child: Icon(Icons.add),
),
);
}
}
🔹 Real-time Subscriptions
Listen to database changes in real-time:
class RealtimeNotesScreen extends StatefulWidget {
@override
_RealtimeNotesScreenState createState() => _RealtimeNotesScreenState();
}
class _RealtimeNotesScreenState extends State<RealtimeNotesScreen> {
final supabase = Supabase.instance.client;
List<Map<String, dynamic>> notes = [];
@override
void initState() {
super.initState();
_loadNotes();
_subscribeToNotes();
}
Future<void> _loadNotes() async {
final data = await supabase
.from('notes')
.select()
.order('created_at', ascending: false);
setState(() => notes = List<Map<String, dynamic>>.from(data));
}
void _subscribeToNotes() {
supabase
.from('notes')
.stream(primaryKey: ['id'])
.listen((data) {
setState(() {
notes = List<Map<String, dynamic>>.from(data);
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Real-time Notes')),
body: ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) {
final note = notes[index];
return Card(
child: ListTile(
title: Text(note['title']),
subtitle: Text(note['content']),
),
);
},
),
);
}
}
Real-time updates:
✓ Automatic UI updates on data changes
✓ No manual refresh needed
✓ Perfect for collaborative apps
🔹 File Storage
Upload and download files:
import 'dart:io';
import 'package:image_picker/image_picker.dart';
class StorageExample extends StatelessWidget {
final supabase = Supabase.instance.client;
Future<void> uploadAvatar() async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
if (image == null) return;
final file = File(image.path);
final userId = supabase.auth.currentUser!.id;
final fileName = '$userId.jpg';
await supabase.storage
.from('avatars')
.upload(fileName, file, fileOptions: FileOptions(upsert: true));
// Get public URL
final imageUrl = supabase.storage
.from('avatars')
.getPublicUrl(fileName);
print('Image uploaded: $imageUrl');
}
Future<void> downloadFile(String fileName) async {
final data = await supabase.storage
.from('avatars')
.download(fileName);
// Save to device or display
print('File downloaded: ${data.length} bytes');
}
Future<void> deleteFile(String fileName) async {
await supabase.storage
.from('avatars')
.remove([fileName]);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('File Storage')),
body: Center(
child: ElevatedButton(
onPressed: uploadAvatar,
child: Text('Upload Avatar'),
),
),
);
}
}