Getting Started with Supabase and Flutter: An Overview

Mateusz Szczudłowski

flutter and supbase

In our series about Flutter app development, we’ve discussed a wide range of topics, including using this framework to create animations in mobile apps, things that every aspiring Flutter developer should know, and digital products built using this technology. In today’s blog post, I’d like to explore the process of integrating Flutter with Supabase - here’s my take on how you should complete it.

If you’d like to know more about how we approach cross-platform development at Monterail, here’s where you’ll find more information:

Cta image

What is Supabase?

Supabase is an open-source alternative to Firebase, offering a plethora of services such as authentication, database, storage, and more. With Flutter gaining traction as a popular framework for developing cross-platform applications, integrating Supabase with Flutter is an excellent choice for developers seeking to build feature-rich apps. In this guide, we will walk you through the process of setting up a Supabase project with Flutter, covering everything from creating a new project to handling user authentication and working with databases.

Introduction to Supabase and Flutter

Supabase is an open-source alternative to Firebase that provides a suite of backend services, such as authentication, database management (PostgreSQL), storage, and functions. It supports various authentication providers, including email, Apple, Azure, Discord, GitHub, GitLab, Bitbucket, Slack, and more. This makes Supabase a great choice for developers who need a broader range of authentication options or prefer a SQL database over a NoSQL one.

On the other hand, Flutter is a popular framework developed by Google for creating cross-platform applications. It allows developers to write code once and deploy it on multiple platforms such as Android, iOS, web, macOS, and Windows, without compromising on performance, UI, or UX.

Integrating Supabase with Flutter enables developers to build feature-rich applications that leverage the power of both platforms. Let’s explore how to set up a Supabase project with Flutter and work with various Supabase services.

Supabase Initial Configuration

Before diving into the Flutter side of things, we need to set up our Supabase project. This involves creating a new project, setting up a database, and configuring authentication.

Creating a New Project

First, visit the Supabase website and create an account if you haven't already. Once logged in, you can create a new organization and project by following these steps:

  1. Click on "New Project" and select your organization (or create a new one by clicking on "New organization").
  2. Fill out the form to create a new project.
  3. You should now see your new project on the dashboard.

Creating a Database with SQL Editor

To create a new database in your project, navigate to the "SQL Editor" tab on the dashboard. Here, you can create a script that sets up a table and defines rules for the database. For this guide, let's create a simple "colors" table with some sample data:


-- Create the table
CREATE TABLE colors (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL
);
-- Insert some sample data into the table
INSERT INTO colors (name) VALUES ('red');
INSERT INTO colors (name) VALUES ('blue');
INSERT INTO colors (name) VALUES ('green');

Click on "RUN" in the bottom right corner to execute the script and create the table with the sample data.

2.3 Supabase Table Editor

Once your database is created, you can use the "Table Editor" tab on the dashboard to view and manage your tables. For example, you should now see the "colors" table with the sample data we added in the previous step.

Authentication Configuration

Supabase supports various authentication providers, and the process can be completed on the "Authentication" tab on the dashboard. In this guide, we will focus on email authentication using deep linking. To set up email authentication:

  1. Go to the "Authentication" tab on the dashboard.
  2. Add a redirect URL for your application in the "Redirect URLs" section and click on "Add URL". 

Setting up a Flutter Project

To get started with your Flutter project, follow these steps:

  1. Install Flutter on your machine if you haven't already. You can follow the official Flutter installation guide for detailed instructions.
  2. Create a new Flutter project using the flutter create command. You can skip this step if you already have a working setup.

Installing Supabase Client Library

To work with Supabase in your Flutter app, you need to install the supabase_flutter client library. This library provides a convenient interface for interacting with Supabase services from a Flutter app. To install the library, add it as a dependency in your pubspec.yaml file:

dependencies:
  supabase_flutter: ^1.10.3

Then, import the library in your main Dart file and initialize Supabase with your project's URL and anonymous key, you can find them in the "Project Settings" tab on the dashboard and then navigate to "API".

import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Supabase.initialize(
    url: 'YOUR_SUPABASE_URL',
    anonKey: 'YOUR_SUPABASE_ANON_KEY',
  );
  runApp(MyApp());
}

Querying data from the app

With the Supabase client library installed and initialized, you can now query data from your app. For example, you can use a FutureBuilder to fetch data when the home page loads and display the query results in a ListView:

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
  final _future = Supabase.instance.client
      .from('colors')
      .select<List<Map<String, dynamic>>>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: _future,
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }
          final colors = snapshot.data!;
          return ListView.builder(
            itemCount: colors.length,
            itemBuilder: ((context, index) {
              final color = colors[index];
              return ListTile(
                title: Text(color['name']),
              );
            }),
          );
        },
      ),
    );
  }
}

Now, when you run your app, it should display a list of colors fetched from your Supabase database.

Supabase Authentication Deep Dive

Supabase offers various authentication options, and in this section, we will delve deeper into email authentication and third-party logins.

Email and password authentication

To implement email and password authentication, you can use the signInWithPassword method provided by the Supabase client library. Here's an example of how you can implement email authentication:

Future<void> signInWithEmail() async { 
  final AuthResponse res = await supabase.auth.signInWithPassword( 
   email: '[email protected]',
   password: 'example-password'
  );
}

Third-party logins

Supabase supports various third-party login providers, such as Google, Apple, and GitHub. To implement third-party logins, you can use the signInWithOAuth method provided by the Supabase client library. This method automatically launches the auth URL and opens a browser for the user to sign in with their chosen provider.

For example, to implement Google login, you can use the following code:

async function signInWithGoogle() { const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google', }) }
supabase.auth.signInWithOAuth(
  Provider.google,
  redirectTo: 'io.supabase.flutter://login-callback/',
);

Note that you will need to configure the login provider in your Supabase project's authentication settings for this to work. You can follow the official Supabase guide on setting up third-party logins for more details.

Working with Real-Time Data

Supabase provides real-time data capabilities, allowing you to receive live updates from your database. This can be useful for implementing features such as chat, notifications, and live updates in your app.

Real-time data as stream

To receive real-time updates, you first need to enable Realtime in your Supabase console. You can read more about enabling Realtime in the official Supabase documentation.

Once Realtime is enabled, you can use the stream() method to subscribe to updates from your database. When used with a StreamBuilder, this allows your app to update its UI in response to changes in the data.

Here's an example of how to use a StreamBuilder to display real-time updates for the colors list:

class _MyWidgetState extends State<MyWidget> {
  final SupabaseStreamBuilder _stream = supabase
      .from('colors')
      .stream(primaryKey: ['id']);
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Map<String, dynamic>>>(
      stream: _stream,
      builder: (context, snapshot) {
        // return your widget with the data from snapshot
      },
    );
  }
}

Subscribing to database changes

You can also subscribe to specific changes in your Supabase tables using the channel() method. This allows you to perform custom actions or update your app's state in response to changes in the data.

Here's an example of how to subscribe to changes in the "colors" table:

final myChannel = supabase.channel('my_channel');
myChannel.on(
    RealtimeListenTypes.postgresChanges,
    ChannelFilter(
      event: '*',
      schema: 'public',
      table: 'colors',
    ), (payload, [ref]) {
  // Do something fun or interesting when there is a change in the database
}).subscribe();

Implementing Storage

Supabase provides storage capabilities that allow you to store and manage files, such as images, videos, and documents. In this section, we'll explore how to upload files to Supabase storage.

Create a bucket

To create the storage, you have to find the "Storage" tab on the dashboard and then navigate to "New bucket".

Uploading files

To upload files to Supabase storage, you can use the upload() method provided by the Supabase client library. This method takes a file path and a file object as arguments and uploads the file to the specified path in your Supabase storage.

Here's an example of how to upload a file to Supabase storage:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        final file = File('example.txt');
        file.writeAsStringSync('File content');
        supabase.storage
            .from('my_bucket')
            .upload('my/path/to/files/example.txt', file);
      },
      child: const Text('Upload'),
    );
  }
}

Supabase Functions

Supabase Functions allows you to run serverless functions in response to HTTP requests. This is useful for running server-side logic without needing to manage a server.

to extend the functionality of your app by running serverless functions in response to events or on-demand. Supabase Functions are a bit too complex for this showcase but it is good to know they are there if you need them.

Customizing LocalStorage

By default, the supabase_flutter package uses the Hive library to persist user sessions. However, you can customize this behavior by implementing your own LocalStorage class.

Using Hive for encryption

To use Hive for encryption, you need to set an encryption key before initializing Supabase:

Future<void> main() async {
  // Set the encryption key before initializing
  HiveLocalStorage.encryptionKey = 'my_secure_key';
  await Supabase.initialize(...);
}

Note that the key must be the same across sessions. There is no check for the correctness of the encryption key. If the key is incorrect, unexpected behavior may occur. You can learn more about encryption in Hive in the official Hive documentation.

Using Flutter secure storage

To use the flutter_secure_storage plugin to store user sessions in secure storage, you can create a custom LocalStorage implementation as follows:

// Define the custom LocalStorage implementation
class SecureLocalStorage extends LocalStorage {
  SecureLocalStorage() : super(
    initialize: () async {},
    hasAccessToken: () {
      const storage = FlutterSecureStorage();
      return storage.containsKey(key: supabasePersistSessionKey);
    }, accessToken: () {
      const storage = FlutterSecureStorage();
      return storage.read(key: supabasePersistSessionKey);
    }, removePersistedSession: () {
      const storage = FlutterSecureStorage();
      return storage.delete(key: supabasePersistSessionKey);
    }, persistSession: (String value) {
      const storage = FlutterSecureStorage();
      return storage.write(key: supabasePersistSessionKey, value: value);
    },
  );
}
// Use the custom LocalStorage implementation when initializing Supabase
Supabase.initialize(
  ...
  localStorage: SecureLocalStorage(),
);

To disable session persistence, you can use the EmptyLocalStorage class:

Supabase.initialize(
  // ...
  localStorage: const EmptyLocalStorage(),
);

Supabase supports deep links on Android, iOS, web, macOS, and Windows. Below, we'll explore how to set up deep links for each platform for example to use Magic Link.

To set up deep links on Android, you need to modify your AndroidManifest.xml file as follows:

<manifest ...>
  <!-- ... other tags -->
  <application ...>
    <activity ...>
      <!-- ... other tags -->
      <!-- Deep Links -->
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
        <data
          android:scheme="[YOUR_SCHEME]"
          android:host="[YOUR_HOST]" />
      </intent-filter>
    </activity>
  </application>
</manifest>

The android:host attribute is optional for deep links.

For more information, refer to the Android developer documentation on deep linking.

To set up deep links on iOS, you need to modify your ios/Runner/Info.plist file as follows:

<!-- ... other tags -->
<plist>
  <dict>
    <!-- ... other tags -->
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>[YOUR_SCHEME]</string>
        </array>
      </dict>
    </array>
    <!-- ... other tags -->
  </dict>
</plist>

This will allow your app to be launched using YOUR_SCHEME://ANYTHING links.

For more information, refer to the Apple developer documentation on defining a custom URL scheme.

Setting up deep links on Windows requires a few more steps than other platforms. You can follow the app_links package documentation for detailed instructions.

In summary, you need to modify your windows/runner/win32window.h and windows/runner/win32window.cpp files and register your custom URL scheme in the Windows registry. You can use the url_protocol package to register the scheme from within your app, or include the registry modifications in your installer.

To set up deep links on macOS, you need to modify your macos/Runner/Info.plist file as follows:

<!-- ... other tags -->
<plist version="1.0">
  <dict>
    <!-- ... other tags -->
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string>sample_name</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>sample</string>
            </array>
        </dict>
    </array>
    <!-- ... other tags -->
  </dict>
</plist>

Conclusion

In this overview, we have explored how to set up a Supabase project with Flutter and work with various Supabase services, such as authentication, databases, and storage. By leveraging the power of Supabase and Flutter, developers can build feature-rich, cross-platform applications that cater to a wide range of use cases. With a strong foundation in Supabase and Flutter development, you can create your projects and explore more advanced concepts and features. I recommend you try a combination of these two great technologies.

Mateusz Szczudłowski avatar
Mateusz Szczudłowski