Flutter & Supabase: A Match Made In Heaven
Flutter & Supabase: A Match Made in Heaven
Hey everyone! Are you ready to dive into the awesome world of building apps with Flutter and Supabase? Supabase is like a super cool, open-source alternative to Firebase. It gives you a backend as a service (BaaS) that’s packed with features, making it super easy to build your app without spending ages on backend stuff. And Flutter , well, it’s Google’s amazing UI toolkit for crafting beautiful apps for mobile, web, and desktop from a single codebase. When you put these two together, you get a powerhouse for rapid app development! In this article, we’ll walk through everything you need to know to get started, from setting up your Supabase project to connecting it with your Flutter app and performing some basic operations. Let’s get started, shall we?
Table of Contents
Setting up Supabase
First things first, you’ll need a Supabase account. Head over to
supabase.com
and sign up. It’s totally free to start, which is a massive win for experimenting and learning. Once you’re in, create a new project. You’ll be asked to pick a name, choose a region (pick the one closest to you), and set a password. After the project is created, you’ll land on your project dashboard. This is where the magic happens! On the left-hand side, you’ll see a navigation menu with different sections:
Dashboard
,
SQL Editor
,
Authentication
,
Storage
, and
Database
. Each of these sections lets you control a specific part of your app’s backend. Before jumping into Flutter, let’s create a database table to store some data. Click on the ‘Table Editor’ in the ‘Database’ section. Create a new table called
items
(or whatever you like, but remember it!).
Now, let’s add some columns to this table. The columns we’ll need include:
id
(this will be the primary key, and Supabase automatically creates this for you),
name
(of type
text
),
description
(of type
text
), and
is_completed
(of type
boolean
). After adding these columns, save your table. Now, your database is ready! The next step is to grab your Supabase credentials. Head over to the ‘Settings’ icon (the gear icon) on the left-hand side and click on ‘API’. You’ll see two important keys:
anon public
(this is the public API key that you can safely use in your Flutter app) and
service_role
(this is a super-secret key, which you should NEVER expose in your frontend code). You’ll need the public API key and the project URL to connect Supabase to your Flutter app. Let’s prepare our Flutter project to interact with Supabase; we are almost there, guys!
Creating a Flutter Project
If you haven’t already, install Flutter on your machine by following the instructions on the official Flutter website (
flutter.dev
). Once installed, create a new Flutter project using the following command in your terminal:
flutter create supabase_flutter_app
. Replace
supabase_flutter_app
with your project’s desired name. After the project is created, navigate into the project directory using
cd supabase_flutter_app
. Next, you’ll need to add the Supabase Flutter package to your project. Add the following line to your
pubspec.yaml
file under the
dependencies
section:
supabase_flutter: ^2.0.0 # Check the latest version on pub.dev
Run
flutter pub get
in your terminal to install the package. Now, open your
main.dart
file. Import the Supabase package at the top of the file:
import 'package:supabase_flutter/supabase_flutter.dart';
Then, initialize Supabase in the
main
function before you run your app. Replace ‘YOUR_PROJECT_URL’ and ‘YOUR_ANON_KEY’ with your actual Supabase project URL and anon public key, which you got from the Supabase dashboard:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'YOUR_PROJECT_URL',
anonKey: 'YOUR_ANON_KEY',
);
runApp(MyApp());
}
This initializes Supabase, making it ready to use throughout your app. Let’s move on to setting up some basic UI components!
Setting up the UI
Now that you’ve got Supabase and Flutter talking, let’s create a simple user interface. We’ll start with a basic
ListView
to display items fetched from Supabase. Open your
main.dart
file and start building your UI. We’ll create a
StatefulWidget
to manage the app’s state. Within the
build
method, create a
Scaffold
with an
AppBar
for the title. The
body
of our
Scaffold
will contain a
FutureBuilder
to fetch data from Supabase. The
FutureBuilder
is used to handle asynchronous operations, like fetching data from a remote server. It displays a loading indicator while the data is being fetched and renders the data when it’s available. Inside the
FutureBuilder
, we’ll call a function to fetch the data from the Supabase database. This is where you connect to the Supabase database. The
future
property of the
FutureBuilder
will be assigned a
Future
that fetches the data. The
builder
property takes a function that builds the UI based on the state of the future (loading, error, or data). Inside the
builder
function, check the
snapshot.connectionState
. If it’s
ConnectionState.waiting
, show a loading indicator. If there’s an error, display an error message. If the data is available (
ConnectionState.done
), display the items in a
ListView
. For each item, create a
ListTile
to display the item’s details.
Here’s a basic structure of the
FutureBuilder
within the
Scaffold
:
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _getTodos(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final todos = snapshot.data ?? [];
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo['name'] as String),
subtitle: Text(todo['description'] as String),
trailing: Checkbox(
value: todo['is_completed'] as bool,
onChanged: (bool? value) {
// Handle checkbox changes later
},
),
);
},
);
},
),
Now, let’s create the
_getTodos
function that fetches data from Supabase. We’ll use
Supabase.instance.client.from('items').select('*').execute()
to fetch all items from the
items
table. Make sure to replace
'items'
with the name of your table. The
select('*')
part means that we want to fetch all columns. Finally, return the result. Don’t worry, we’ll implement this function shortly, so you’re not lost. This fetches the data, and the
FutureBuilder
displays it in our UI! Pretty cool, right? Time to implement this
_getTodos
function.
Implementing _getTodos Function
Let’s now implement the
_getTodos
function, which fetches the to-do items from your Supabase database. This function uses the Supabase client to query the
items
table. It returns a
Future
that resolves to a list of maps, where each map represents a to-do item with its properties. Here’s the code for the
_getTodos
function:
Future<List<Map<String, dynamic>>> _getTodos() async {
final response = await Supabase.instance.client
.from('items')
.select('*')
.execute();
if (response.error != null) {
print('Error fetching items: ${response.error!.message}');
return [];
}
return List<Map<String, dynamic>>.from(response.data as List);
}
This function queries the Supabase database to retrieve all items from your
items
table and is designed to handle potential errors gracefully. It utilizes the Supabase client to construct a query that fetches all columns (
*
) from the table. The
execute()
method runs the query, and the result is stored in the
response
variable. The function checks for any errors that may have occurred during the query execution. If an error is detected, a message is printed to the console, and an empty list is returned. If no errors occur, the function converts the returned data into a list of maps, where each map represents a single item with its properties. These properties correspond to the columns in your database table. Returning a list of maps makes it easy to work with data in the Flutter app. Once the items are fetched, you can iterate through this list to display the data in your UI, as shown in the previous section. With this implementation, you have a solid foundation for displaying data from your Supabase database in your Flutter application.
Interacting with the Database
Now, let’s learn how to interact with the database – how to insert, update, and delete items. This will make your app much more dynamic and useful! First, let’s add a button to the UI to add new items. You can add a floating action button to the
Scaffold
:
floatingActionButton: FloatingActionButton(
onPressed: () {
// Handle adding new items
},
child: const Icon(Icons.add),
),
In the
onPressed
of the
FloatingActionButton
, you will show a dialog or a new screen to input the item details (name, description, etc.). Once the user has entered the details, call a function to insert the item into the database.
Adding New Items
To insert data, you’ll use the
Supabase.instance.client.from('items').insert({ ... })
method. This method allows you to insert a single record or multiple records into a specified table. Inside the
insert
method, you pass a map representing the data you want to insert. This map should contain keys that match the column names of your database table and the values you want to insert into those columns. The function returns a
Future
that resolves after the insertion is complete. You can handle any errors by checking the
response.error
property after the insertion. Here is an example of adding a new item:
Future<void> addItem(String name, String description) async {
final response = await Supabase.instance.client.from('items').insert({
'name': name,
'description': description,
'is_completed': false,
});
if (response.error != null) {
print('Error inserting item: ${response.error!.message}');
}
}
Updating Items
To update items, you use the
Supabase.instance.client.from('items').update({ ... }).eq('id', itemId)
method. This method allows you to update existing records in your table. The
update
method takes a map containing the column names and the new values that you want to set for the record. For example, to update the ‘is_completed’ field of an item:
Future<void> updateItem(int itemId, bool isCompleted) async {
final response = await Supabase.instance.client
.from('items')
.update({'is_completed': isCompleted})
.eq('id', itemId);
if (response.error != null) {
print('Error updating item: ${response.error!.message}');
}
}
Deleting Items
Finally, to delete items, use the
Supabase.instance.client.from('items').delete().eq('id', itemId)
method. The
delete
method is used to remove records from a table. The
eq
method is used to specify the condition that must be met for a record to be deleted, in this case, the
id
. It will remove the corresponding row from the table:
Future<void> deleteItem(int itemId) async {
final response = await Supabase.instance.client
.from('items')
.delete()
.eq('id', itemId);
if (response.error != null) {
print('Error deleting item: ${response.error!.message}');
}
}
Authentication with Supabase
Supabase makes user authentication super easy. It supports various authentication methods, including email/password, social logins (Google, GitHub, etc.), and phone authentication. Let’s see how to implement email/password authentication in your Flutter app. First, you need to enable email/password authentication in your Supabase project. Go to the ‘Authentication’ section in your Supabase dashboard and then to ‘Settings’. Enable ‘Email/Password’ under ‘Authentication Providers’. Make sure to set up your email configuration (sender email, etc.) so that users can receive verification emails. In your Flutter app, you’ll use the
Supabase.instance.client.auth
methods to handle authentication. For example, to sign up a new user, you use:
final AuthResponse res = await Supabase.instance.client.auth.signUp(
email: 'user@example.com',
password: 'your_password',
);
To log in an existing user, you use:
final AuthResponse res = await Supabase.instance.client.auth.signInWithPassword(
email: 'user@example.com',
password: 'your_password',
);
You should handle the results of these operations (the
AuthResponse
) to check if the sign-up or sign-in was successful. If an error occurs, the
session
property will be null, and you can check the
error
property for details. You can also implement a sign-out function:
await Supabase.instance.client.auth.signOut();
. Implement UI elements such as sign-up and login forms in your Flutter app, and connect them with the Supabase authentication methods. Remember to handle the user’s session and store it securely within your app. It’s a good practice to automatically redirect users to the appropriate screen after they sign in or sign up. This makes for a great user experience!
Conclusion
So there you have it, folks! Using Supabase with Flutter is a fantastic way to build robust and scalable applications quickly. We’ve covered the basics of setting up Supabase, connecting it to your Flutter app, fetching and displaying data, and even adding authentication. Supabase simplifies the backend, and Flutter allows you to build amazing user interfaces. This combination is ideal for both beginners and experienced developers. As you get more comfortable, explore advanced Supabase features, such as real-time updates and storage. Happy coding, and have fun building your awesome apps! If you have any questions or want to dive deeper into Supabase and Flutter, feel free to ask. Cheers!