Mobile App Development

Supabase As A Backend For Flutter Apps Step-By-Step Guide

16th Apr 2024
globe

Supabase, an open-source substitute for Firebase, provides several services like authentication, database management, storage, and more. Flutter is becoming a more well-liked framework for creating cross-platform apps; thus, combining Supabase with Flutter is an excellent option for developers who want to create many features. This tutorial will take you step-by-step through configuring a Flutter project for Supabase, covering everything from project creation to managing user authentication and database operations.

Also when it comes to building Flutter apps, it's always a good idea to connect with an expert  Flutter app development company in India.

Overview Of Flutter And Supabase

A range of backend services, including authentication, database administration (PostgreSQL), storage, and functions, are offered by Supabase, an open-source substitute for Firebase. Because of this, developers who require a wider variety of authentication choices or who like SQL databases over NoSQL ones should consider Supabase.

However, Flutter is a well-liked framework that Google created for making cross-platform apps. It lets developers create code only once and publish it to various platforms, including Windows, macOS, iOS, Android, and the web, without sacrificing UI, UX, or performance.

Features Of Supabase:

1. Supabase for Data Management

Managing data in your Flutter app is made easier using Supabase. To execute queries, inserts, updates, and deletions, utilize the SupabaseClient class. You may also use the real-time functionality to subscribe to database updates, guaranteeing that your app's data is updated in real time.

2. Supabase Authentication: A Secure Approach for Your Flutter App

With Supabase's integrated authentication features, you may verify users using various techniques, including social logins (Facebook, Google, and so on) and email and password.

Step-by-Step Guide For Flutter Apps

1. Initial Supabase Configuration

We must set up our Supabase project before beginning to work with Flutter.

1.1 Starting a New Initiative

Go to the Supabase website and register if you still need to. After logging in, you can establish a new project and organization by doing the following:

  • Choose "New Organization" to start a new group, or "New Project" to collaborate with your present one
  • The project you just established should now be visible on the dashboard.

1.2 How to Make a Database using SQL Editor

  • The database schema will now be configured.
  • This is where a script can be written to generate a table and set the database's rules.

-- 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');

  1. Press the "RUN" button located in the lower right corner to initiate the script and generate a table containing the sample data.
  2. Acquire the API Keys#
  3. You may now use the auto-generated API to insert data after creating some database tables.
  4. Open the Dashboard and navigate to the API Settings tab.

1.3 Supabase Table Editor

Once your database has been created, you may use the dashboard's "Table Editor" option to view and control your tables. For example, you should be able to see the sample data that we provided in the previous step in the "colors" table.

1.4 Authentication Configuration

The "Authentication" tab on the dashboard is where you may finish the procedure, and Supabase supports many authentication providers. In this guide, we will concentrate on deep linking email authentication. To configure email authentication - Open the dashboard and select the "Authentication" tab.

Once you have started working on your Flutter project, do the following:

  • Install Flutter on your computer if it still needs to be installed
  • If you have a functional setup already, you can skip this step.

Now, let's get the Flutter app built from the ground up

  • Launch a Flutter application
  • We may initialize an app named by using Flutter create.

supabase_quickstart:

Next, let us install the solitary extra dependency: superbase_flutter Superbase Flutter: ~2.0.0 Run flutter pub to install the dependencies.

2. Setup deep links

Establish deep links# Now that the requirements are installed, let's get started with the deep link setup. Deep links must be set up so the user can return to the app after clicking the magic link to sign in. We can set up deep links with just a slight adjustment to our Flutter application.

We have to use io.supabase.flutter quickstart as the scheme.As an illustration, we'll use login-callback as the deep link's host, but you are free to modify it to suit your preferences.

First, add io.supabase.flutter quickstart://login-callback/ as a new redirect URL in the Dashboard.

2.1 Installing Supabase Client Library

Installing the supabase_flutter client library is required to use Supabase in your Flutter application. With the help of this library, a Flutter app may easily communicate with Supabase services.

dependencies: supabase_flutter: ^1.10.3

Next, initialize Supabase by passing in the URL and anonymous key of your project and importing the library into your main Dart code. The "API" page is where you may find these after selecting the "Project Settings" tab on the dashboard.

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());}

2.2 Querying Data From the App

As an illustration, you can use a FutureBuilder to retrieve data upon page load and present 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']),);}),);},),);}}

3. Supabase Authentication Deep Dive

We will go into more detail about email authentication and third-party logins in this section. Supabase provides a number of authentication alternatives.

login. dart

class LoginPage extends StatefulWidget {

const LoginPage({super.key, this.supabase});

final SupabaseClient? supabase;

@override <br> LoginPageState createState() => LoginPageState();} class LoginPageState extends State<LoginPage> { Future<void> _signIn() async { try {debugPrint("EMAIL: ${_emailController.text}, PASSS: ${_passwordController.text}"); await widget.supabase?.auth.signInWithPassword(email: _emailController.text, password: _passwordController.text); if (mounted) { _emailController.clear();_passwordController.clear(); _redirecting = true; Navigator.of(context).pushReplacementNamed('/home');}} on AuthException catch (error) {context.showErrorSnackBar(message: error.message);} catch (error) {context.showErrorSnackBar(message: 'Unexpected error occurred');}} <br><br> @override

Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Center(child: Text('Login')), backgroundColor: Colors.teal), body: SingleChildScrollView(Padding(padding: const EdgeInsets.only(top: 25.0),child: Container(height: 50, width: 250, decoration: BoxDecoration(color: Colors.teal, borderRadius: BorderRadius.circular(20)),child: TextButton(// style: ButtonStyle(backgroundColor: MaterialStateColor.resolveWith((states) => Colors.teal), ),onPressed: () async {if (_formKey.currentState!.validate()) {_signIn();}},child: const Text('Login',style: TextStyle(color: Colors.white, fontSize: 25),),),),),const SizedBox(height: 130,),TextButton(onPressed: () {Navigator.push(context, MaterialPageRoute(builder: (_) => // RegisterUser(supabase: widget.supabase ?? Supabase.instance.client)SignUpPage(supabase: widget.supabase ?? Supabase.instance.client)));},child: const Text('Don\'t have an account?', style: TextStyle(color: Colors.teal),)), const SizedBox( height: 30,),),);}signup.dart class SignUpPage extends StatefulWidget {const SignUpPage({super.key, required this.supabase}); final SupabaseClient supabase;

3.1 Email and password authentication

We also need to make some changes to the signup page and login page because we changed our Supabase's configuration to send a confirmation email.

import 'dart:async'; import 'package:flutter/material.dart'; import 'package:my_chat_app/pages/login_page.dart'; import 'package:my_chat_app/pages/rooms_page.dart'; import 'package:my_chat_app/utils/constants.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class RegisterPage extends StatefulWidget { const RegisterPage( {Key? key, required this.isRegistering}): super(key: key); static Route<void> route({bool isRegistering = false}) { return MaterialPageRoute( builder: (context) => RegisterPage(isRegistering: isRegistering),);} final book isRegistering;

@override

State<RegisterPage> createState() => _RegisterPageState();} class _RegisterPageState extends State<RegisterPage> { final bool _isLoading = false; final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _usernameController = TextEditingController(); late final StreamSubscription<AuthState> _authSubscription;

@override

void initState() { super.initState(); bool haveNavigated = false; // Listen to auth state to redirect user when the user clicks on confirmation link _authSubscription = supabase.auth.onAuthStateChange.listen((data) { final session = data.session; if (session != null && !haveNavigated) { haveNavigated = true; Navigator.of(context) .pushReplacement(RoomsPage.route());}});}

@override

void dispose() { super.dispose(); // Dispose subscription when no longer needed _authSubscription.cancel();} Future<void> _signUp() async { final isValid = _formKey.currentState!.validate();if (!isValid) {return;} final email = _emailController.text; final password = _passwordController.text; final username = _usernameController.text; try {await supabase.auth.signUp(email: email,password: password,data: {'username': username},emailRedirectTo: 'io.supabase.chat://login',);context.showSnackBar(message: 'Please check your inbox for confirmation email.');} on AuthException catch (error) {context.showErrorSnackBar(message: error.message);} catch (error) {debugPrint(error.toString());context.showErrorSnackBar(message: unexpectedErrorMessage);}}

@override

Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Register'),),body: Form(key: _formKey,child: ListView(padding: formPadding,children: [TextFormField(controller: _emailController,decoration: const InputDecoration(label: Text('Email'),),validator: (val) {if (val == null || val.isEmpty) {return 'Required';}return null;}, keyboardType: TextInputType.emailAddress,),spacer,TextFormField(controller: _passwordController,obscureText: true,decoration: const InputDecoration(label: Text('Password'),),validator: (val) {if (val == null || val.isEmpty) {return 'Required';}if (val.length < 6) {return '6 characters minimum';}return null;},),spacer,TextFormField(controller: _usernameController,decoration: const InputDecoration(label: Text('Username'),),validator: (val) {if (val == null || val.isEmpty) {return 'Required';}final isValid =RegExp(r'^[A-Za-z0-9_]{3,24}$').hasMatch(val);if (!isValid) {return '3-24 long with alphanumericor underscore';}return null;},),spacer,ElevatedButton(onPressed: _isLoading ? null : _signUp,child: const Text('Register'),),spacer,TextButton(onPressed: () {Navigator.of(context).push(LoginPage.route());},child:const Text('I already have an account'))],),),);}}

The login screen gets easier to use. It simply logs users in by using their password and email address. There is absolutely no navigation being done by it. This is due to the fact that LoginPage is accessed on top of RegisterPage; as the auth state listener on RegisterPage is still active, it can handle navigation.

import 'package:flutter/material.dart'; import 'package:my_chat_app/utils/constants.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class LoginPage extends StatefulWidget { const LoginPage({Key? key}) : super(key: key); static Route<void> route() { return MaterialPageRoute( builder: (context) => const LoginPage());}

@override _LoginPageState createState() => _LoginPageState();} class _LoginPageState extends State<LoginPage> { bool _isLoading = false; final _emailController = TextEditingController(); final _passwordController = TextEditingController(); Future<void> _signIn() async {setState(() {_isLoading = true;}); try {await supabase.auth.signInWithPassword( email: _emailController.text, password: _passwordController.text,);} on AuthException catch (error) {context.showErrorSnackBar(message: error.message);} catch (_) {context.showErrorSnackBar(message: unexpectedErrorMessage);} if (mounted) {setState(() {_isLoading = true;});}}

@override

void dispose() {_emailController.dispose();_passwordController.dispose();super.dispose();}

@override

Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Sign In')),body: ListView(padding: formPadding,children: [ Text Form Field(controller: _emailController, decoration:const Input Decoration(label Text: 'Email'),keyboardType: TextInputType.emailAddress,),

3.2 Third-party logins

Supabase supports GitHub, Apple, Google, and other third-party login providers. You can use the Supabase client library's signInWithOAuth method to implement third-party logins. Using this strategy, users can sign in with their preferred provider by opening a browser and automatically launching the auth URL.

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

Remember that for this to function, you must set up the login provider in the authentication settings of your Supabase project. You can set up third-party logins by following the official Supabase guide for further information.

4. Working with Real-Time Data

You can get real-time updates from your database with Supabase's real-time data capabilities. This might be useful when adding features to your program, such as chat, notifications, and real time changes.

4.1 Data in real-time as a stream

You must first enable real-time on your Supabase console in order to receive real-time updates. Once Realtime is enabled, you may subscribe to updates from your database using the stream() method.This is an illustration of how to display data using a StreamBuilder.

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

4.2 Subscribing to Database Changes

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();

5. Implementing Storage

Using Supabase's storage tools, you may organize and keep files of all kinds, including photos, movies, and documents. In this section, we'll examine uploading files to Supabase storage.

5.1 Create a Bucket

To build the storage, find the "Storage" option on the dashboard and choose "New bucket".

5.2 Uploading Files

To upload files to Supabase storage, use the upload() function provided by the Supabase client library. This function takes two parameters: a file path and a file object, and uses these to upload the file to the specified directory in your Supabase storage.

An example of uploading a file to Supabase storage may be found here. Code:

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'), }, ); } // Use the custom LocalStorage implementation when initializing Supabase Supabase.initialize(...localStorage: SecureLocalStorage(),

6. Supabase Functions

Using serverless functions that execute on-demand or in reaction to events, you may increase the functionality of your application with Supabase Functions. Although Supabase Functions are a bit too sophisticated for this showcase, it's still helpful to know they exist.

7. Customizing LocalStorage

You can, however, alter this behavior by creating a custom LocalStorage class.

7.1 Using Hive for Encryption

Prior to initialization Supabase, you must set an encryption key in order to use Hive for encryption:

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

Keep in mind that every session needs to use the same key. The accuracy of the encryption key is not verified. If the key is off, strange things could happen.

7.2 Using Flutter Secure Storage

You can make a custom LocalStorage implementation in the manner described below to utilize the flutter_secure_storage plugin to store user sessions in secure storage:

// 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( // &hellip; localStorage: const EmptyLocalStorage(), );

8. Deep Link Configuration Examples

Deep connections are supported by Supabase on Windows, macOS, iOS, and Android platforms.

8.1 Setting up Deep Links on Android

You must make the following changes to your AndroidManifest.xml file in order to configure deep links on Android:

<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>

For deep links, the android:host attribute is not required.

See the deep linking documentation for Android developers for additional details.

8.2 Setting up Deep Links on iOS

It would help if you made the following adjustments to your ios/Runner/Info.list file in order to configure deep links on iOS:

< !-- ... 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.

8.3 Setting up Deep Links on Windows

Compared to other platforms, Windows requires a few extra steps to set up deep links. For comprehensive instructions, refer to the documentation provided by the app_links package.

The registry changes can be included in your installer, or you can register the scheme from within your program using the url_protocol package.

8.4 Setting up Deep Links on macOS

You must make the following changes to your macos/Runner/Info.plist file in order to configure deep links on macOS:

< !-- ... 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>

Final Output:-

Now, the email you used to sign up needs to be validated in order for you to log in successfully. Once the email has been verified, you can go back to the app.

Conclusion:

With Supabase and Flutter, developers have a powerful combination for versatile backend services and efficient app development across platforms. Supabase provides authentication, real-time database management, and storage features, while Flutter simplifies the creation of high-quality apps for different platforms.

By following this comprehensive guide, developers can easily integrate Supabase into their Flutter projects, allowing them to build feature-rich applications effortlessly. Working with a skilled Flutter app development company in India can also improve the development process and deliver top-notch solutions.

Read more articles

globe

Laravel vs React: Which One Fits Your Project Best?

The success of your project might be determined by the framework you choose in t...

globe

Protecting Your Cloud Infrastructure using AWS Security Best Practices

To protect your data and ensure the availability and integrity of your AWS resou...

globe

Supabase As A Backend For Flutter Apps Step-By-Step Guide

Supabase, an open-source substitute for Firebase, provides several services l...