Sitemap
Arpandev

Arpandev

I post random tech 😁!

Mastering Supabase with Flutter(Part 1)

16 min readDec 26, 2023

--

Press enter or click to view image in full size

Introduction:

Hey everyone, Welcome to Mastering Superbase with flutter(part 1) tutorial where I will guide you to learn Supabase in most possible easy way. So Let’s Get Started.

So Now Question comes up in everyone’s mind What’s Supabase actually?

In simple terms, Supabase is an open-source Firebase alternative that provides a set of tools and services to help developers build scalable and secure applications. It is often compared to Firebase, as it offers a similar set of features for backend development but with an open-source and extensible approach. Here are some key aspects of Supabase.

a) Database: Supabase includes a real-time relational database, allowing you to store and retrieve data for your applications.

b) Authentication: It provides user authentication services, enabling you to manage user sign-ups, logins, and access control for your application.

c) Real-time: Supabase supports real-time updates, meaning that changes to your data are instantly reflected to all connected clients, providing a responsive and dynamic user experience.

d) RESTful API: Supabase offers a RESTful API, making it easy to integrate with different frontend frameworks and programming languages.

e) Data Security: It supports row-level security, allowing you to define rules to control which users can access specific rows of data in your database.

f) Serverless Functions: Supabase allows you to deploy serverless functions, enabling you to run custom server-side code without managing the infrastructure.

g) File Storage: It provides file storage capabilities, allowing you to store and serve files for your applications.

So now one question comes everyone’s mind. Is it free?

No, Supabase offers a free tier with certain usage limitations. it’s essential to check the latest pricing and features on the https://supabase.com/pricing because pricing models and plans can be subject to change.

The free tier typically includes a set amount of resources, such as storage, database usage, and bandwidth. If your application’s usage exceeds the limits of the free tier, you may need to consider one of Supabase’s paid plans. Note: In this tutorial, We will use free tier to start with.

What’s the key difference between firebase & supabase?

Supabase is an open-source BaaS platform with a relational database, real-time capabilities, and customizable authentication, while Firebase, owned by Google, is a closed-source BaaS offering NoSQL databases, extensive real-time features, and a comprehensive authentication system. The choice depends on preferences for openness, data modeling, and specific feature requirements.

why one should use supabase over the firebase ?

Choose Supabase over Firebase if you prioritize an open-source solution with a relational database, transparent pricing, and customizable authentication, providing greater control and flexibility in your backend development.

So Now we will try to learn how to integrate Supabase in Flutter

Getting Started

Step1: Create a new Flutter Project

I am assuming that you have already installed flutter SDK in your system. Now The first thing to do is creating a new flutter project. Open terminal at their desired location and type the command: Flutter create flutter_supabase .You can use any project name. I have simply named it “flutter_supabase”. In order to open your application in vs code, type the command: cd flutter_supabase.

Step2: Create a Supabase Account

Go to the official website for Supabase and sign in with your Github account. Official url : www.supabase.com

Press enter or click to view image in full size

Step3: Setup a new Supabase Project

Once signed in, you will be taken to dashboard, where you can create a new project. Like this image below-

Press enter or click to view image in full size
  1. Enter your project name and a strong database password and click Create new Project.

2. To create a new project, you need to have an organization, if you don’t have one yet, you can create it yourself there.

3. After creating the project, it will take two to three minutes to build the database and the API.

Press enter or click to view image in full size

Step4: Get the API Keys

• It will take few minutes to setup

• Then It will a page with lots of info in it about your supabase project.

  • Copy your Project URL and the Anon key there. We will need this for the next step.
Press enter or click to view image in full size

The anon key is a public key that we can use in browsers while the URL is a RESTful endpoint for querying and managing the database.

Step5: Install Dependencies in our Flutter App

Head over to https://pub.dev & add below required dependency in your flutter app.

supabase_flutter: ^2.0.2 
flutter_dotenv: ^5.1.0

Step6: Create a dot env file to store our supabase credentials

· In the root folder of our app, just create a file with the name “.env”

· After that add the .env file to the assets within pubspec.yaml:

SUPABASE_URL = ENTER SUPABASE URL
SUPABASE_KEY = ENTER SUPABASE KEY
 assets:
- .env

Note: A .env file, short for “environment,” is a plain text configuration file commonly used in software development projects. It is designed to store environment variables, which are key-value pairs used to configure aspects of an application’s behavior. Environment variables are often used to store sensitive information or configuration settings that can vary between different environments (e.g., development, testing, production).

Step7: Initialize our App

Now that we have everything ready let’s initialize the Supabase client inside our main function with the API credentials that you copied earlier. Just make the following changes in the file. (main.dart).

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// load env
await dotenv.load();
// initialize supabase
String supabaseUrl = dotenv.env['SUPABASE_URL'] ?? '';
String supabaseKey = dotenv.env['SUPABASE_KEY'] ?? '';
await Supabase.initialize(url: supabaseUrl,anonKey: supabaseKey);
runApp(const MyApp());
}

Step:8 Creating Files and Folders

We will structure our project into folders instead of writing everything at one file. Create a folder inside lib and name it pages And inside pages, create 2 files : home_page.dart and start_page.dart.

home_page.dart:

import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(child: Text("Home Page"),));
}
}

start_page.dart:

import 'package:flutter/material.dart';
class StartPage extends StatelessWidget {
const StartPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:const Center(child: Text("Start Page"),));
}
}

Topic1:User Authentication Using Supabase

Step1: Setup Auth Screen

(1) Create a new folder ‘Auth’ under lib folder & create a dart file with Stateful widget by the name AuthPage

(2) We will show pages to user according to whether user is already authenticated or not.

authpage.dart:

import 'package:flutter/material.dart';
import 'package:flutter_supabase/pages/homepage.dart';
import 'package:flutter_supabase/pages/start_page.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class AuthPage extends StatefulWidget {
const AuthPage({super.key});

@override
State<AuthPage> createState() => _AuthPageState();
}

class _AuthPageState extends State<AuthPage> {
/*Retrieve the current user and assign the value to the _user variable. Notice that
this page sets up a listener on the user's auth state using onAuthStateChange. */
User? _user;
@override
void initState() {
_getAuth();
super.initState();
}

Future<void> _getAuth() async {
setState(() {
_user = supabase.auth.currentUser;
});
supabase.auth.onAuthStateChange.listen((event) {
setState(() {
_user = event.session?.user;
});
});
}

final SupabaseClient supabase = Supabase.instance.client;
@override
Widget build(BuildContext context) {
return _user == null ? StartPage() : HomePage();
}
}

main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_supabase/Auth/authpage.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
// load env
await dotenv.load();
// initialize supabase
String supabaseUrl = dotenv.env['SUPABASE_URL'] ?? '';
String supabaseKey = dotenv.env['SUPABASE_KEY'] ?? '';
await Supabase.initialize(url: supabaseUrl, anonKey: supabaseKey);
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.lightGreen,
useMaterial3: true,
),
home: AuthPage(),
);
}
}

Note: We have already created the homepage & startpage. Lib folder structure is given below.

Step2: Run the application

  1. Start your app in a virtual device or your own physical device.
  2. If there is no error then that means Supabase is successfully configured.

Note:

  1. The App will run successfully. And If you are getting such issue or warning in your console like mine
Press enter or click to view image in full size

2. If you face this error, you can change the compileSdkVersion in android/app/build.gradle file & check

Press enter or click to view image in full size

Step3: Lets work on our Start Page

This is the page where user can login as well as register. Make it a stateful widget and start with creating some states.

Start_page.dart:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class StartPage extends StatefulWidget {
const StartPage({super.key});

@override
State<StartPage> createState() => _StartPageState();
}

class _StartPageState extends State<StartPage> {
final SupabaseClient supabase = Supabase.instance.client;
bool _signInLoading = false;
bool _signUpLoading = false;
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text("StartPage"),
),
);
}
}

Step4: Lets work on the user interface

start_page.dart:

Scaffold(
body: Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(
20.0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.network(
'https://seeklogo.com/images/S/supabase-logo-DCC676FFE2-seeklogo.com.png',
height: 150.0,
),
const SizedBox(height: 25.0),
//Email field
TextFormField(),
//Password field
TextFormField(),
const SizedBox(height: 25.0),
//Sign In Button
ElevatedButton(
onPressed: () {},
child: Text(
"Sign In",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
const Divider(),
//SignUp Button
OutlinedButton(
onPressed: () {},
child: Text(
"Sign Up",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 25.0),
Center(child: const Text("Credit: Developed by Arpandev"))
],
),
),
),
),
),
);

Output:

Step5:Decorate TextFormField

start_page.dart:

                 //Email field
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Field is required';
}
return null;
},
controller: _emailController,
decoration: const InputDecoration(label: Text("Email")),
keyboardType: TextInputType.emailAddress,
),
//Password field
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Field is required';
}
return null;
},
controller: _passwordController,
decoration: const InputDecoration(label: Text("Password")),
obscureText: true,
),

Output:

Step6: Understand Supabase Sign Up Syntax

Press enter or click to view image in full size

Step7: Sign Up Functionality

start_page.dart:

//SignUp Button
_signInLoading
? const Center(child: CircularProgressIndicator())
: OutlinedButton(
onPressed: () async {
final isValid = _formKey.currentState?.validate();
if (isValid != true) {
return;
}
setState(() {
_signUpLoading = true;
});
try {
await supabase.auth.signUp(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Sucess!Confirmtion mail send"),
backgroundColor: Colors.green,
),
);
setState(() {
_signUpLoading = false;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Oops!Sign up Failed!"),
backgroundColor:
Color.fromARGB(255, 243, 49, 6),
),
);
setState(() {
_signUpLoading = false;
});
}
},
child: Text(
"Sign Up",
style: TextStyle(fontWeight: FontWeight.bold),
),
)

Here’s a step-by-step explanation of SignUp functionality:

1. The code first checks if a loading variable `_signUpLoading` is true. If it is, it displays a `CircularProgressIndicator` to indicate that a process is ongoing. If not, it displays an `OutlinedButton` which the user can interact with.

2. The `OutlinedButton` has an `onPressed` function which is triggered when the user taps the button. This function is asynchronous, meaning it can perform operations that may take some time to complete, such as network requests.

3. Inside the `onPressed` function, it first validates the form associated with `_formKey`. If the form is not valid, it immediately returns and does not proceed with the rest of the function.

Get Arpandev’s stories in your inbox

Join Medium for free to get updates from this writer.

4. If the form is valid, it sets `_signUpLoading` to true, indicating that the sign-up process has started.

5. It then tries to sign up the user using the `supabase.auth.signUp` method, passing the email and password from their respective controllers.

6. If the sign-up is successful, it shows a `SnackBar` with a success message and sets `_signUpLoading` to false, indicating that the sign-up process has ended.

7. If there’s an error during the sign-up process (caught by the `catch` block), it shows a `SnackBar` with an error message and also sets `_signUpLoading` to false.

8. The `OutlinedButton` also has a child widget which is a `Text` widget displaying “Sign Up” with bold font weight. This is what the user sees on the button.

Note: Supabase by default enables email verification, which means that when using Supabase authentication with this setting turned on, your users are required to verify their email addresses in order for their accounts to become active. You can disable it as well going to the setting option the Authentication section.

Step8: Sign In Functionality

start_page.dart

//Sign In Button
_signInLoading
? const Center(
child: CircularProgressIndicator(),
)
: ElevatedButton(
onPressed: () async {
final isValid = _formKey.currentState?.validate();
if (isValid != true) {
return;
}
setState(() {
_signInLoading = true;
});
try {
await supabase.auth.signInWithPassword(
email: _emailController.text.trim(),
password: _passwordController.text.trim());
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Oops!Sign IN Failed!"),
backgroundColor:
Color.fromARGB(255, 243, 49, 6),
),
);
setState(() {
_signInLoading = false;
});
}
},
child: Text(
"Sign In",
style: TextStyle(fontWeight: FontWeight.bold),
),
),

Here’s a step-by-step explanationof Sign In functionality :

1. The code first checks if `_signInLoading` is true. If it is, it displays a `CircularProgressIndicator` in the center of the screen. This is typically used to indicate that the app is busy, such as when it’s waiting for a response from a server.

2. If `_signInLoading` is not true, it displays an `ElevatedButton` widget, which is a Material Design raised button.

3. When the button is pressed, it first validates the form associated with `_formKey`. If the form is not valid, it returns immediately and does nothing else.

4. If the form is valid, it sets `_signInLoading` to true and updates the UI. This will cause the `CircularProgressIndicator` to be displayed.

5. It then tries to sign in the user using the `signInWithPassword` method of the `supabase.auth` object. It passes the trimmed text from `_emailController` and `_passwordController` as the email and password.

6. If the sign in fails (i.e., an exception is thrown), it displays a `SnackBar` with a message saying “Oops! Sign IN Failed!”. The background color of the `SnackBar` is set to a specific color. It also sets `_signInLoading` to false and updates the UI, which will hide the `CircularProgressIndicator`.

7. The text on the button is “Sign In”, and it’s styled with a bold font weight.

Final output & testing the functionality

Press enter or click to view image in full size
Press enter or click to view image in full size

Step9:Deep link Integration

Now that we have email password authentication working, let’s setup deep links so users who have logged in via magic link or external Oauth can come back to the app.

First step is to setup redirect url in the Supabase dashboard

• Go to your Supabase Project Dashboard

• Click on the Authentication icon in the left sidebar

• Click on URL Configuration under the Configuration section and you’ll find the Redirect URLs.

Add io.supabase.myflutterapp://login-callback as a new redirect URL.
Press enter or click to view image in full size

NOTE: You can rename ‘myflutterapp’ in io.supabase.myflutterapp and also rename login-callback I guess. But I tried changing io.supabase to something else and it gave me error while redirecting

Step10:In this step For Deep-link Integration, you have to add the below line of codes

For Android, edit the android/app/src/main/AndroidManifest.xml file.

<!-- Add this intent-filter for 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="io.supabase.myflutterapp"
android:host="login-callback" />
</intent-filter>

NOTE: After that Stop the application and run again.

Step11:Sign Out Functionality

home_page.dart:

class _HomePageState extends State<HomePage> {
final SupabaseClient supabase = Supabase.instance.client;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () async {
await supabase.auth.signOut();
},
icon: const Icon(Icons.logout),
),
],
),
body: Center(
child: Text("Home page"),
),
);
}
}
Press enter or click to view image in full size

Topic 2.Google OAuth:

Step1: Implement Google Authentication

To enable Google Auth for your project, you need to set up a Google OAuth application and add the application credentials to your Supabase Dashboard.

Step (a): Create and configure a new Google Project on the Google Cloud Platform Console

• Go to cloud.google.com

• Click on Sign in at the top right to log in if you are not already signed in by default & click on “Go to console”.

• Click on Select a Project at the top left.

• Click New Project at the top right.

• Fill in your app information, then click Create.

• This should bring you to the dashboard for your new project

or if required select the new project from the notification.

Press enter or click to view image in full size

Step b: Create the OAuth Keys for your project

• In your project dashboard, in the search bar at the top type Oauth

• Click on OAuth consent screen from the list of results

• Select the option External from user type and click create

Next on the Edit app registration page, fill out your app information. Mostly the required fields and then click on save and continue.

Press enter or click to view image in full size

Step c: Find your callback URL

• Go to your Supabase Project Dashboard

• Click on the Project Setting icon in the bottom left sidebar

• Select the option API

Now you can see the Project URL

Press enter or click to view image in full size

Add /auth/v1/callback next to the project url & make it like below-

https://hxansgnpqpwrbswuaagw.supabase.co/auth/v1/callback

Step d: Create your Google credentials

• Click Credentials at the left to go to the Credentials page on the Google Cloud Platform console

• Click Create Credentials near the top then select OAuth client ID

• On the Create OAuth client ID page, select your application type. If you’re not sure, choose Web application

• Fill in your app name and then at the bottom, under Authorized redirect URIs click Add URI

• Paste your callback URI under Authorized redirect URIs which you copied earlier and click on create

Copy and save the values under Your Client ID and Your Client Secret

Press enter or click to view image in full size

Step e: Enter your Google credentials into your Supabase Project

• Go to your Supabase Project Dashboard

• In the left sidebar, click the Authentication

• Click on Providers under the Configuration section

• Click on Google from the accordion list to expand and turn Google Enabled to ON

Enter your Google Client ID and Google Client Secret and click Save.

Press enter or click to view image in full size

You have completed all the steps and successfully configured google sign in method.

Step 2: Adding Google Sign In button in our project

Now everything is done, it’s time we implement google sign in in our Flutter app. First lets create a button in my start page I have used network image as well as remove the earlier divider.

start_page.dart:Inside of children[] under the sign up button, integrate the code below code:

 Row(
children: const [
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.all(15.0),
child: Text("OR"),
),
Expanded(child: Divider()),
],
),
OutlinedButton.icon(
onPressed: () {},
icon: Image.network(
"https://cdn.freebiesupply.com/logos/large/2x/google-icon-logo-png-transparent.png",
height: 20,
),
label: const Text("Continue with Google"),
),

Output:

Step3: Implement Google Sign in Functionality

start_page.dart:

                  _googleSignInLoading
? const Center(
child: CircularProgressIndicator(),
)
: OutlinedButton.icon(
onPressed: () async {
setState(() {
_googleSignInLoading = true;
});
try {
await supabase.auth.signInWithOAuth(
OAuthProvider.google,
redirectTo: kIsWeb
? null
: 'io.supabase.myflutterapp://login-callback/');
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Oops!Failed to SignIn with Google, try normal sign up"),
backgroundColor:
Color.fromARGB(255, 243, 49, 6),
),
);
setState(() {
_googleSignInLoading = false;
});
}
},
icon: Image.network(
"https://cdn.freebiesupply.com/logos/large/2x/google-icon-logo-png-transparent.png",
height: 20,
),
label: const Text("Continue with Google"),
),

This code is implementing Google OAuth sign-in functionality in a Flutter application using the Supabase library. Here’s a line-by-line explanation:

1. `_googleSignInLoading ? const Center(child: CircularProgressIndicator(),) : OutlinedButton.icon(`: This line is using a ternary operator to check if the Google sign-in process is loading. If it is (`_googleSignInLoading` is true), it displays a loading spinner (`CircularProgressIndicator`). If it’s not, it displays a button (`OutlinedButton.icon`).

2. `onPressed: () async {`: This is the function that will be called when the button is pressed. It’s marked as `async` because it will be performing asynchronous operations.

3. `setState(() { _googleSignInLoading = true; });`: This line sets the `_googleSignInLoading` variable to true, indicating that the sign-in process has started.

4. `await supabase.auth.signInWithOAuth(OAuthProvider.google, redirectTo: kIsWeb ? null : ‘io.supabase.myflutterapp://login-callback/’);`: This line is calling the `signInWithOAuth` method from the Supabase library, which starts the Google OAuth sign-in process. The `redirectTo` parameter is used to specify where the user should be redirected after the sign-in process. If the app is running on the web (`kIsWeb` is true), it doesn’t need to redirect, so it’s set to `null`. Otherwise, it redirects to a custom URL scheme.

5. `catch (e) {`: This is the start of a catch block that will handle any errors that occur during the sign-in process.

6. `ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text(“Oops!Failed to SignIn with Google, try normal sign up”), backgroundColor: Color.fromARGB(255, 243, 49, 6),),);`: If an error occurs, this line displays a snackbar with a message indicating that the sign-in process failed.

7. `setState(() { _googleSignInLoading = false; });`: This line sets the `_googleSignInLoading` variable back to false, indicating that the sign-in process has ended.

8.icon:Image.network(“https://cdn.freebiesupply.com/logos/large/2x/google-icon-logo-png-transparent.png", height: 20,), label: const Text(“Continue with Google”),),`: This line sets the icon and label of the button. The icon is an image loaded from a URL, and the label is a text widget with the string “Continue with Google”.

Output:

Part 2 Coming soon………………………………………..

--

--