Supabase Database with Flutter: Building Powerful Apps with Real-Time Functionality
Introduction
Due to its impressive performance and ease of use, Flutter is a popular option for creating cross-platform mobile apps. Supabase is a great solution for integrating a robust database backend into your Flutter application. This blog will explore Supabase, and show you how to use its features to provide your Flutter application with a powerful database. Let’s get started!
What is Supabase?
To meet the needs of today’s users, it is important to build powerful and responsive apps. When it comes to building data-driven apps with real-time functionality, having a robust, scalable backend becomes crucial. Supabase is an open-source Backend-as-a-Service solution (BaaS), which combines Firebase with traditional databases. It’s built on PostgreSQL, and adds features such as real-time access and authentication. Supabase is a real-time, scalable and secure database that integrates seamlessly with Flutter apps.
This blog post will examine the integration of Supabase and Flutter. It allows you to use its real-time authentication and database features to create dynamic and interactive applications. We will explore the core concepts of Supabase, and show how it allows developers to build applications that scale easily while maintaining data security and integrity.
This guide is for all Flutter developers, whether you are a seasoned developer or just getting started. It will give you a thorough understanding of Supabase’s integration with Flutter. You’ll have the skills to create powerful real-time apps that are backed up by a scalable and reliable database.
Features
Managing Data with Supabase
Supabase simplifies data management in your Flutter app. You can use the SupabaseClient class to perform queries, inserts, updates, and deletions. Additionally, you can leverage the real-time functionality to subscribe to changes in the database, ensuring that your app’s data remains up-to-date in real-time.
Flutter App with Supabase Authentication
The authentication of users is essential for the majority of applications. Supabase has built-in authentication tools that allow you to authenticate your users using a variety of methods, including email/passwords, social logins, (Google, Facebook etc.) and more. Supabase offers built-in authentication features that allow you to authenticate users through various methods like email/password, social logins (Google, Facebook, etc.), and more. We’ll walk you through the process of implementing Supabase to implement secure user authentication for your Flutter application.
Optimizing Performance with Supabase Indexes
Indexes are essential for optimizing the performance of a database. Supabase allows you to create indexes for frequently queried columns. This will improve query response time. We will explore how to select the correct columns to index in your Supabase Database.
Getting Started with Supabase
You need to create a Supabase Project before you can use Supabase with your Flutter application. Sign up for an account on the dashboard, and create a new project.
You will receive an API key and URL once your project has been set up. These are essential to access the Superbase database.
To get the URL and API key, follow the below guidelines:
After successfully signing in and creating your project, go to the Home option
Integration of Supabase into Flutter
It’s now time to integrate your Supabase app into your Flutter application. This can be done using the Supabase Dart Package, which offers a set of APIs for interacting with the Supabase Backend. These APIs allow you to perform CRUD operations and manage user authentication.
You can also subscribe to real-time updates. To do this, follow the steps below:
In the pubspec.yaml of your Flutter project, import the latest version of the supabase_flutter packages.
The Supabase URL and API Key are required to initialize the Supabase connection in Flutter.
Code snippet
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://***.supabase.co',
anonKey: '***'
);
final supabase = Supabase.instance.client;
runApp(ProviderScope(child: App(supabase: supabase)));
}
Code implementation
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: '',
anonKey:
'eyJ bGc...',
);
await AppPreference().initialAppPreference();
final supabase = Supabase.instance.client;
runApp(ProviderScope(child: App(supabase: supabase)));
}
class App extends StatelessWidget {
const App({Key? key, required this.supabase}) : super(key: key);
final SupabaseClient supabase;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/', routes: {
'/': (_) => SplashPage(supabase: supabase),
'/login': (_) => LoginPage(supabase: supabase),
'/register': (_) => RegisterUser(supabase: supabase),
'/home': (_) => HomeScreen(),
// home: Home(supabase: supabase),
});
}
}
Authentication
login. dart
class LoginPage extends StatefulWidget {
const
LoginPage({super.key, this.supabase});
final
SupabaseClient? supabase;
@override
LoginPageState
createState() => LoginPageState();
}
class
LoginPageState extends State {
...
Future
_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');
}
}
@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;
@override
SignUpPageState
createState() => SignUpPageState();
}
class
SignUpPageState extends State {
...
Future
_signUp() async {
try
{
AuthResponse
response = await widget.supabase.auth.signUp(
password:
_passwordController.text, email: _emailController.text);
if
(mounted) {
_redirecting
= true;
print("Userrr
-- ${response.user}");
_saveId(response.user);
Navigator.of(context).pushReplacementNamed("/register").then(
(value)
=> context.showSnackBar(message: "Verify your email!"));
setState(()
{});
}
}
on AuthException catch (error) {
context.showErrorSnackBar(message:
error.message);
}
catch (error) {
context.showErrorSnackBar(message:
'Unexpected error occurred');
}
}
@override
Widget
build(BuildContext context) {
return
Scaffold(
appBar:
AppBar(
title:
const Text('Sign Up'),
backgroundColor:
Colors.teal,
),
body:
SingleChildScrollView(
child:
...
Container(
height:
50,
width:
250,
decoration:
BoxDecoration(
color:
Colors.teal,
borderRadius:
BorderRadius.circular(20)),
child:
TextButton(
onPressed:
() {
if
(_formKey.currentState!.validate()) {
if
(_passwordController.text ==
_confPasswordController.text)
{
_signUp();
}
else {
ScaffoldMessenger.of(context).showSnackBar(
const
SnackBar(
content:
Text(
"Passwords
didn't match! Try again.")));
}
}
},
child:
const Text(
'Sign
Up',
style:
TextStyle(color: Colors.white, fontSize: 25),
),
),
),
const
SizedBox(
height:
130,
),
...
}
Final Output: 1
Final Output: 2
Table of Contents
Tags Cloud
Frequently Asked Questions (FAQs)
How to Handle Offline Data Storage with Flutter Hive?
Introduction
The data storage locally is a necessity for virtually all apps. Storage and manipulating data is a vital aspect of developing apps and is the same for Flutter applications. Perhaps you’d like to cache requests from REST APIs, create an application that runs on the internet or store information about customers in a food delivery service.
Several options are available for developers to persist local data in Flutter.shared_preferences: Provides a good way to store small pairs of keys and values .sqlite : It’s a good choice when your database must handle complex relationships between relational data.
But, if you’re searching for a quick and secure local database that is also suitable using Flutter Web(), in this case, to manage offline data storage using Flutter Hive is among the most effective options available.
What is Hive in Flutter?
Hive is a light and lightning fast key-value database created in the pure language of Dart that lets you save and sync your application data offline.
As a key-value storage device created in Dart, Hive supports primitive and intricate data structures while delivering the highest degree of performance. Furthermore, it is secured with AES-256.
To illustrate Here is the graph below that compares Flutter Hive to other similar databases:
Getting Started with Handle Offline Data Storage with Flutter Hive
In this blog post, we’ll examine how to utilize the TypeAdapter in Flutter with the Hive DataBase. Additionally, we’ll create a simple one-page application that displays a user overview, allows you to add new users, update current ones, and remove users.
How to use Flutter Hive to Handle Offline Data Storage?
Step 1: Dependency installation
Two prerequisites are needed before we can utilize Hive.
hive and hive_flutter
You need to add the Hive and hive_flutter packages to pubspec.yaml as follows:
dependencies:
Flutter:
sdk: flutter
hive: ^2.2.3
hive_flutter: ^1.1.0
Add the dev dependencies
dev_dependencies:
flutter_test:
sdk: flutter
hive_generator: ^1.1.3
build_runner: ^2.2.0
Step 2: Initialization Hive Database
The first step is to initialize Hive prior to calling runApp in the Flutter app.
void main() async{
WidgetsFlutterBinding.ensureInitialized();
// Initializes Hive with a valid directory in your app files
await Hive.initFlutter();
runApp(const MyApp());
}
The initFlutter() function is provided by Hive.Basically, it initializes Hive by using the path returned by getApplicationDocumentsDirectory
Do you need help with a fast and secure local database with no native dependencies?
Profit from the benefits of Hivean easy key-value database that stores information locally. You will immediately see the benefits over Sqlite because Hive lets you modify the data on the devices you want to use it. Hire Flutter Developer
Box in Hive
Here’s how you can deal with offline data storage using Flutter Hive.
The data that is stored within Flutter Hive are arranged into boxes. The box is akin to the table that is used in SQL but does not have a structure and is able to contain everything. As I explained in my introduction Hive secures data.Additionally these boxes are able to be used to store sensitive data.
Utilizing key-value sets, Hive keeps its information. The first step is to open the box.
void main() async{
WidgetsFlutterBinding.ensureInitialized();
// Initializes Hive with a valid directory in your app files
await Hive.initFlutter();
// open box
await Hive.openBox("userBox");
runApp(const MyApp());
}
Model class with TypeAdapter
Our example contains several users with information such as name, hobby, and description.
import 'package:hive/hive.dart';
part 'user_model.g.dart';
@HiveType(typeId: 0)
class UserModel extends HiveObject {
@HiveField(0)
final String name;
@HiveField(1)
final String hobby;
@HiveField(2)
final String description;
UserModel({
required this.name,
required this.hobby,
required this.description,
});
}
The first step is to import the hive generator package. In order to generate the type adapter, add a section called user_model.g.dart.TypeAdapter does not need to be constructed manually since we are using the hive generator package.
It automatically builds TypeAdapters for virtually any class by using the hive_generator software pack You can observe that the userModel class has been notated with a variety of fields
@HiveType(): Use @HiveType() to make the model class obvious so the generator knows that this is supposed to be a TypeAdapter.
@HiveField(index): Notifying the fields of the class by a field with the associated index is required.
To construct a TypeAdapter class, run the following command. flutter packages pub run build_runner build
This file’s name is user_model.dart and the data_model.g.dart files will also be included, where the word “g” stands for generated. This means that user_model.g.dart is the new generated file.
It’s time to sign up for UserModelAdapter as it’s been successfully built
To achieve this, we have to create that adapter before running the app’s run function.
void main() async{
WidgetsFlutterBinding.ensureInitialized();
// Initializes Hive with a valid directory in your app files
await Hive.initFlutter();
// Register Hive Adapter
Hive.registerAdapter(UserModelAdapter());
// open box
await Hive.openBox("userBox");
runApp(const MyApp());
}
CRUD operations
Creating Data in Hive
You can use the reference to the Hive box to add data by calling add() function.A key-value pair is accepted by this method.
/// Add new user
Future addUser({required UserModel userModel}) async {
await box.add(userModel);
}
The dialog will appear when we press the floating button. Here, you can type in names, hobbies and descriptions. Following that, we click the add button and then the information will show.
The ValuelistenableBuilder() stream in Flutter Hive can also be used to listen to what is happening inside the box.
Retrieving Data in Hive
Box objects can be read by using the get() method. To retrieve its value, you simply need to provide the key, like this
var userHobby = box.get('hobby');
In case you are using auto-incrementing values, you can use the getAt(index) method of the box object to read using the index,like this
var userData = box.getAt(index);
ValueListenableBuilder(
valueListenable: HiveDataStore.box.listenable(),
builder: (context, Box box, widget) {
return SafeArea(
child: box.length > 0 ? ListView.builder(
shrinkWrap: true,
itemCount: box.length,
itemBuilder: (BuildContext context, int index) {
var userData = box.getAt(index);
return Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(color: Colors.grey.withOpacity(0.1),
border: Border.all(color: Colors.blue.shade 900),
borderRadius: const BorderRadius.all(Radius.circular(10))),
child: Row(
children: [
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IntrinsicHeight(
child: Row(
children: [
Text(userData.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
),
VerticalDivider(color: Colors.blue.shade 900,thickness: 2,),
Text(userData.description, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
],
),
),
const SizedBox(height: 15),
RichText(text: TextSpan(text: 'Hobby: ', style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w700),
children: [
TextSpan(text: userData.hobby, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
],
),
),
],
),
),
Expanded(
flex: 0,
child: Row(
children: [
InkWell(
onTap:(){
isUpdate.value = true;
nameEditingCtr.text = userData.name;
hobbyEditingCtr.text = userData.hobby;
descriptionEditingCtr.text = userData.description;
_showDialog(context,index);
},
child: Icon(Icons.edit, size: 30, color: Colors.blue.shade 900,),
),
const SizedBox(width: 10),
InkWell(
onTap: ()async{
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure you want to delete ${userData.name}?'),
actions: [
TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue.shade 900),
elevation: MaterialStateProperty.all(3),
shadowColor: MaterialStateProperty.all(Colors.blue.shade 900), //Defines shadowColor
),
onPressed: () {dataStore.deleteUser(index: index);},
child: const Text('Yes', style: TextStyle(color: Colors.white),
),
),
TextButton(
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.blue.shade 900),
elevation: MaterialStateProperty.all(3),
shadowColor: MaterialStateProperty.all(Colors.blue.shade 900), //Defines shadowColor
),
onPressed: () {Navigator.of(context, rootNavigator: true).pop(); },
child: const Text('No',
style: TextStyle(color: Colors.white),
),
),
],
),
);
},
child: Icon(Icons.delete,size:30,color: Colors.blue.shade 900,))
],
)),
],
),
);
}):const Center(child: Text("No Data Found"),));
}
)
Updating Data in Hive
The put() method can update the data you originally stored for a key.In this way, the newly provided value will be updated at that key.
/// update user data
Future updateUser({required int index,required UserModel userModel}) async {
await box.putAt(index,userModel);
}
Here we have used the auto-incrementing values, you can use the putAt(index) method of the box object to update using the index.
Deleting Data in Hive
In order to delete data, you can pass the key to the delete() method.
/// delete user
Future deleteUser({required int index}) async {
await box.deleteAt(index);
}
Here we have used the auto-incrementing values, you can use the deleteAt(index) method of the box object to delete using the index.
LazyBox
Each time we design the regular box, the contents are recorded in memory.Performance is higher as a result.
The LazyBox is a fantastic way to quickly access data when you have a lot of data within an archive and don’t wish to put it all in memory.
var lazyBox = await Hive.openLazyBox('myLazyBox');
var value = await lazyBox.get('lazyVal');
Box Compression
We have now completed most of the coding for the app. It’s time to clean up: the Hive is an append-only store.It is possible to manually use the .compact() method or let Hive handle it for us.
As a result, I have overridden the dispose method in order to close the Openbox.
@override
void dispose(){
// to free up space
Hive.box('userBox').compact();
// close all the open boxes before closing the page.
Hive.close();
}