Build A Real-Time Chat App With Flutter And Supabase
Build a Real-Time Chat App with Flutter and Supabase
Let’s dive into creating a real-time chat application using Flutter for the frontend and Supabase for the backend. This combination offers a powerful and efficient way to build modern, scalable chat applications. We’ll cover everything from setting up your Supabase project to designing the Flutter UI and implementing real-time messaging.
Table of Contents
- Setting Up Supabase
- Creating a New Project
- Defining the Database Schema
- Configuring Authentication
- Building the Flutter UI
- Setting Up the Flutter Project
- Implementing the Login Screen
- Designing the Chat List Screen
- Implementing the Chat Screen
- Implementing Real-Time Messaging
- Listening for Changes
- Sending New Messages
- Conclusion
Setting Up Supabase
First, let’s get our Supabase project up and running. Supabase is an open-source Firebase alternative that provides all the necessary tools to build a backend without managing servers. Guys, this is where the magic begins! We’ll create a new project, set up our database schema, and configure authentication. These steps are crucial for securing and managing our chat application’s data and users.
Creating a New Project
Head over to the Supabase website and sign up for an account. Once you’re in, create a new project. You’ll need to choose a name, a database password, and a region for your project. The region should be geographically close to your users to minimize latency. Supabase will then provision a new PostgreSQL database for you, along with all the necessary backend services.
Defining the Database Schema
With your Supabase project ready, it’s time to define the database schema for our chat application. We’ll need at least two tables: one for users and another for messages. The users table will store user information such as ID, username, and email. The messages table will store the chat messages, including the message content, sender ID, and timestamp. Open the Supabase dashboard and navigate to the SQL editor. Here, you can execute SQL commands to create and manage your tables. Here’s an example of how to create the messages table:
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
sender_id UUID NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc', now())
);
This SQL code creates a
messages
table with columns for
id
,
sender_id
,
content
, and
created_at
. The
id
is a UUID (Universally Unique Identifier) that automatically generates a unique ID for each message. The
sender_id
is a foreign key that references the
users
table, allowing us to identify the sender of each message. The
content
column stores the actual message text, and the
created_at
column stores the timestamp of when the message was sent. You can add more fields as per your requirements.
Configuring Authentication
Authentication is a critical aspect of any chat application. Supabase provides built-in authentication services, making it easy to manage user sign-ups, logins, and password resets. In the Supabase dashboard, navigate to the Authentication section and enable the authentication providers you want to support, such as email/password, Google, and GitHub. Configure the necessary settings for each provider, such as API keys and redirect URLs. Supabase handles the complexities of authentication, allowing you to focus on building the chat application’s core features. You can also define custom roles and permissions to control access to different parts of your application.
Building the Flutter UI
Now that our Supabase backend is set up, let’s move on to building the Flutter UI for our chat application. We’ll create the necessary screens, such as the login screen, the chat list screen, and the chat screen. We’ll also implement the UI components for displaying messages, sending messages, and managing user profiles. Flutter’s rich set of widgets and declarative UI approach make it easy to create beautiful and responsive user interfaces. Alright, let’s get coding!
Setting Up the Flutter Project
Create a new Flutter project using the Flutter CLI:
flutter create flutter_supabase_chat
cd flutter_supabase_chat
Add the necessary dependencies to your
pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
supabase_flutter: ^1.0.0
Run
flutter pub get
to install the dependencies. The
supabase_flutter
package provides the necessary tools to interact with our Supabase backend.
Implementing the Login Screen
The login screen allows users to authenticate with our Supabase backend. We’ll create a simple UI with email and password fields, along with buttons for signing up and logging in. Use the
supabase_flutter
package to handle the authentication logic. Here’s an example of how to implement the login screen:
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class LoginScreen extends StatefulWidget {
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
Future<void> _signIn() async {
try {
await Supabase.instance.client.auth.signInWithPassword(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
} catch (error) {
print('Error signing in: $error');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _signIn,
child: Text('Login'),
),
],
),
),
);
}
}
This code creates a simple login screen with email and password fields. The
_signIn
function uses the
Supabase.instance.client.auth.signInWithPassword
method to authenticate the user with the provided credentials. You can add error handling and validation to improve the user experience.
Designing the Chat List Screen
The chat list screen displays a list of active chat conversations. We’ll fetch the list of conversations from our Supabase backend and display them in a list view. Each item in the list will show the name of the conversation and the last message sent. Tapping on a conversation will navigate the user to the chat screen. Here’s a basic example:
// Pseudo-code for chat list
ListView.builder(
itemCount: chats.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(chats[index].name),
subtitle: Text(chats[index].lastMessage),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ChatScreen(chatId: chats[index].id)),
);
},
);
},
);
Implementing the Chat Screen
The chat screen displays the messages in a conversation and allows users to send new messages. We’ll use a
StreamBuilder
to listen for real-time updates from our Supabase backend and display the messages in a list view. The bottom of the screen will have a text input field and a send button. When the user sends a message, we’ll store it in the Supabase database and trigger a real-time update to all connected clients. It is important to make the UI user-friendly and easy to understand.
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class ChatScreen extends StatefulWidget {
final String chatId;
ChatScreen({required this.chatId});
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _messageController = TextEditingController();
Future<void> _sendMessage() async {
try {
await Supabase.instance.client
.from('messages')
.insert({
'chat_id': widget.chatId,
'sender_id': Supabase.instance.client.auth.currentUser!.id,
'content': _messageController.text.trim(),
});
_messageController.clear();
} catch (error) {
print('Error sending message: $error');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Chat')),
body: Column(
children: [
Expanded(
child: StreamBuilder(
stream: Supabase.instance.client
.from('messages')
.stream(filter: (query) => query.eq('chat_id', widget.chatId))
.order('created_at'),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final messages = snapshot.data as List<dynamic>;
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
return ListTile(
title: Text(message['content']),
subtitle: Text('Sender: ${message['sender_id']}'),
);
},
);
},
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(hintText: 'Type a message...'),
),
),
IconButton(
onPressed: _sendMessage,
icon: Icon(Icons.send),
),
],
),
),
],
),
);
}
}
This code creates a chat screen that displays messages in real-time. The
StreamBuilder
listens for updates from the
messages
table and rebuilds the list view whenever a new message is added. The
_sendMessage
function inserts a new message into the
messages
table. Remember that the Chat ID references a particular chat you would have created, feel free to set it with a const id or even pass it as an argument.
Implementing Real-Time Messaging
Real-time messaging is the core feature of our chat application. Supabase provides real-time capabilities through its PostgreSQL database and the
supabase_flutter
package. We’ll use the
stream
method to listen for changes in the messages table and update the UI accordingly. This ensures that all connected clients receive new messages in real-time. Real-time messaging enhances user engagement and makes the chat application feel responsive and interactive. This is what truly brings a chat application to life, by allowing live messages across all users.
Listening for Changes
To listen for changes in the messages table, we’ll use the
stream
method provided by the
supabase_flutter
package. This method returns a stream of data that emits a new event whenever the data in the table changes. We can use a
StreamBuilder
widget to listen to this stream and update the UI accordingly. Here’s an example of how to listen for changes in the messages table:
StreamBuilder(
stream: Supabase.instance.client
.from('messages')
.stream(filter: (query) => query.eq('chat_id', widget.chatId))
.order('created_at'),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final messages = snapshot.data as List<dynamic>;
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
return ListTile(
title: Text(message['content']),
subtitle: Text('Sender: ${message['sender_id']}'),
);
},
);
},
);
This code listens for changes in the
messages
table and rebuilds the list view whenever a new message is added. The
filter
method is used to filter the messages based on the
chat_id
, ensuring that we only receive messages for the current conversation. The
order
method is used to sort the messages by
created_at
, ensuring that the messages are displayed in the correct order.
Isn’t that neat?
This part is particularly crucial for making sure the messages are displayed correctly.
Sending New Messages
To send new messages, we’ll use the
insert
method provided by the
supabase_flutter
package. This method allows us to insert a new row into the messages table. When a new message is inserted, Supabase automatically triggers a real-time update to all connected clients. Here’s an example of how to send a new message:
Future<void> _sendMessage() async {
try {
await Supabase.instance.client
.from('messages')
.insert({
'chat_id': widget.chatId,
'sender_id': Supabase.instance.client.auth.currentUser!.id,
'content': _messageController.text.trim(),
});
_messageController.clear();
} catch (error) {
print('Error sending message: $error');
}
}
This code inserts a new message into the
messages
table with the
chat_id
,
sender_id
, and
content
of the message. The
sender_id
is obtained from the current user’s authentication token. The
content
is the text entered by the user in the text input field.
Important Note:
Remember to properly handle errors and validate user inputs to ensure the security and reliability of your application.
Conclusion
Building a real-time chat application with Flutter and Supabase is a rewarding experience. By combining Flutter’s UI capabilities with Supabase’s backend services, you can create a powerful and scalable chat application with real-time messaging. This guide covered the essential steps, from setting up your Supabase project to designing the Flutter UI and implementing real-time messaging. Now it’s your turn to build your own amazing chat application! Good luck, and happy coding! Remember that you can dive into more complex concepts after you get the base structure correct.