Use SupaHost with Flutter
Connect a Flutter app to a SupaHost project with supabase_flutter. Pass keys with --dart-define, initialize the client, query a table, react to auth changes and sign in with an emailed code.
supabase_flutter is the official Flutter package. It wraps the Dart client, stores the session on the device and refreshes tokens for you. It works with SupaHost unchanged: you give it your project URL and anon key.
The examples use the recipes table from the quickstart.
Install
flutter create recipe_box
cd recipe_box
flutter pub add supabase_flutter
Pass your project keys
Flutter has no runtime environment variables, so pass the values at build time with --dart-define. Use the Project URL and anon key from the Connect tab (subtab App):
flutter run \
--dart-define=SUPABASE_URL=https://app-one.acme.supahost.space \
--dart-define=SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
You can also keep them in a JSON file and use --dart-define-from-file=env.json. Leave that file out of version control. The anon key is safe inside an app; the service_role key never is.
Initialize the client
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: const String.fromEnvironment('SUPABASE_URL'),
anonKey: const String.fromEnvironment('SUPABASE_ANON_KEY'),
);
runApp(const RecipeApp());
}
final supabase = Supabase.instance.client;
Query a table
class RecipeList extends StatefulWidget {
const RecipeList({super.key});
@override
State<RecipeList> createState() => _RecipeListState();
}
class _RecipeListState extends State<RecipeList> {
final _recipes = supabase
.from('recipes')
.select('id, title, minutes')
.order('created_at', ascending: false);
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Map<String, dynamic>>>(
future: _recipes,
builder: (context, snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
if (!snapshot.hasData) return const CircularProgressIndicator();
final rows = snapshot.data!;
return ListView.builder(
itemCount: rows.length,
itemBuilder: (context, i) => ListTile(
title: Text(rows[i]['title'] as String),
subtitle: Text('${rows[i]['minutes'] ?? '?'} min'),
),
);
},
);
}
}
Errors from the database, such as a Row Level Security violation, are thrown as PostgrestException. Wrap writes in try/catch:
try {
await supabase.from('recipes').insert({'title': 'Lemon rice', 'minutes': 30});
} on PostgrestException catch (e) {
debugPrint('Insert failed: ${e.message}');
}
The insert only succeeds for a signed-in user, because the quickstart's insert policy is for authenticated.
React to sign-in and sign-out
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<AuthState>(
stream: supabase.auth.onAuthStateChange,
builder: (context, _) {
final session = supabase.auth.currentSession;
return session == null ? const SignInPage() : const RecipeList();
},
);
}
}
Sign in with an emailed code
The emailed one-time code avoids redirects entirely: the magic link email includes a code the user types into the app. If you prefer tapping the link, add your app's scheme (for example recipebox://login) to Redirect URLs on the Auth tab and pass it as emailRedirectTo.
Future<void> sendCode(String email) async {
await supabase.auth.signInWithOtp(email: email);
}
Future<void> verifyCode(String email, String code) async {
await supabase.auth.verifyOTP(
type: OtpType.email,
email: email,
token: code,
);
}
Password sign-in works as well:
await supabase.auth.signInWithPassword(email: email, password: password);
await supabase.auth.signUp(email: email, password: password);
await supabase.auth.signOut();
Both throw AuthException on failure.
Social login
For Google and Apple, use the native sign-in packages (google_sign_in, sign_in_with_apple) to get an ID token, then call supabase.auth.signInWithIdToken(provider: OAuthProvider.google, idToken: idToken). Configure the provider on the Auth tab first. See Sign in with Google and Sign in with Apple.
Browser-based OAuth with signInWithOAuth needs a return address on the Redirect URLs list, which means an https:// link your app opens (Universal Links on iOS, App Links on Android).
Flutter web
On Flutter web the same code works. Add your web origin (for example http://localhost:3000/** and your production URL) under Redirect URLs on the Auth tab, then click Save Auth settings.
Next steps
- Magic links and email OTP
- Postgres Changes with
supabase.channel() - Uploading files
- Can I use SupaHost for a mobile app?