0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Xây Dựng Ứng Dụng Chat Thời Gian Thực với Flutter và Supabase

Đăng vào 1 tuần trước

• 11 phút đọc

Giới Thiệu

Ứng dụng chat thời gian thực là một tính năng quan trọng trong nhiều ứng dụng hiện đại, từ mạng xã hội đến hỗ trợ khách hàng. Việc xây dựng một ứng dụng như vậy từ đầu có thể có vẻ phức tạp, nhưng với công cụ phù hợp, điều này dễ dàng hơn bạn nghĩ. Trong hướng dẫn này, chúng ta sẽ xây dựng một ứng dụng chat hoàn chỉnh sử dụng Flutter cho giao diện người dùng đa nền tảng và Supabase cho backend mạnh mẽ và có thể mở rộng.

Chúng ta sẽ đi qua việc thiết lập dự án, tạo giao diện người dùng và triển khai nhắn tin thời gian thực với cơ sở dữ liệu PostgreSQL mạnh mẽ của Supabase cùng với API Thời gian thực, kèm theo giải thích mã chi tiết.

Tại Sao Chọn Flutter và Supabase?

Trước khi bắt đầu, hãy cùng điểm qua công nghệ mà chúng ta sẽ sử dụng. Flutter cho phép chúng ta xây dựng các ứng dụng đẹp mắt, biên dịch tự nhiên cho di động, web và máy tính để bàn từ một mã nguồn duy nhất. Supabase là một lựa chọn mã nguồn mở thay thế cho Firebase, cung cấp một bộ công cụ backend, bao gồm cơ sở dữ liệu, xác thực và đăng ký thời gian thực, giúp việc bắt đầu dễ dàng hơn bao giờ hết.

Khi kết hợp lại, chúng tạo ra một sự kết hợp mạnh mẽ để xây dựng một ứng dụng chat nhiều tính năng một cách hiệu quả.

Thiết Lập Backend Supabase

Bước đầu tiên là cấu hình backend của chúng ta. Supabase làm cho quá trình này trở nên đơn giản.

1. Tạo Dự Án Supabase

Nếu bạn chưa có tài khoản, hãy truy cập supabase.com và đăng ký. Sau khi vào, hãy tạo một dự án mới. Đặt tên cho nó, tạo một mật khẩu cơ sở dữ liệu an toàn và chọn một khu vực. Dự án của bạn sẽ sẵn sàng trong vài phút.

2. Tạo Bảng messages

Tiếp theo, chúng ta cần một bảng để lưu trữ các tin nhắn chat.

  1. Điều hướng đến Table Editor trong bảng điều khiển Supabase của bạn.

  2. Nhấp vào Tạo bảng mới.

  3. Đặt tên cho bảng là messages.

  4. Tạm thời tắt Row Level Security (RLS) để đơn giản hóa việc phát triển. Chúng ta có thể bật lại khi đưa vào sản xuất.

  5. Thêm các cột sau:

    • id: int8 (khóa chính, được tạo tự động).
    • created_at: timestamptz (giá trị mặc định là now()).
    • content: text.
    • user_id: uuid (để liên kết các tin nhắn với người dùng, giả sử bạn sử dụng Supabase Auth).

3. Bật Tính Năng Realtime

Supabase Realtime lắng nghe các thay đổi trong cơ sở dữ liệu và phát sóng chúng đến các khách hàng kết nối. Để bật tính năng này cho bảng messages:

  1. Đi đến Database > Replication.

  2. Tìm bảng messages và bật nó cho việc sao chép.

Đó là tất cả cho thiết lập backend. Bây giờ hãy chuyển sang ứng dụng Flutter.

Xây Dựng Ứng Dụng Chat Flutter

Với backend đã sẵn sàng, chúng ta có thể bắt đầu xây dựng frontend.

1. Thiết Lập Dự Án và Thư Viện Phụ Thuộc

Tạo một dự án Flutter mới và thêm thư viện Supabase vào tệp pubspec.yaml của bạn.

yaml Copy
dependencies:
  flutter:
    sdk: flutter
  supabase_flutter: ^2.5.0 # Kiểm tra phiên bản mới nhất

Chạy flutter pub get để cài đặt gói. Tiếp theo, khởi tạo Supabase trong tệp main.dart của bạn. Bạn có thể tìm URL Dự án và khóa anon của bạn trong Cài đặt API của dự án Supabase.

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // Cần thiết cho main async

  await Supabase.initialize(
    url: 'YOUR_SUPABASE_URL',
    anonKey: 'YOUR_SUPABASE_ANON_KEY',
  );
  runApp(MyApp());
}

final supabase = Supabase.instance.client;

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ứng Dụng Chat Flutter',
      home: ChatPage(), // Chúng ta sẽ tạo cái này sau
    );
  }
}

2. Xây Dựng Trang Chat

Hãy tạo một tệp mới, chat_page.dart, và xây dựng giao diện người dùng của chúng ta. Chúng ta sẽ sử dụng StatefulWidget để quản lý luồng tin nhắn và bộ điều khiển nhập văn bản.

Chức năng chính của chúng ta là StreamBuilder. Widget này lắng nghe một luồng dữ liệu và xây dựng lại UI của nó mỗi khi có dữ liệu mới đến.

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

class ChatPage extends StatefulWidget {
  @override
  _ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  final _textController = TextEditingController();
  final _messagesStream = Supabase.instance.client
      .from('messages')
      .stream(primaryKey: ['id']).order('created_at');

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  Future<void> _sendMessage() async {
    final content = _textController.text.trim();
    if (content.isEmpty) {
      return;
    }
    _textController.clear();

    final userId = Supabase.instance.client.auth.currentUser?.id;
    if (userId == null) {
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
            content: Text('Bạn phải đăng nhập để gửi tin nhắn.'),
        ));
        return;
    }

    await Supabase.instance.client.from('messages').insert({
      'content': content,
      'user_id': userId,
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Chat Thời Gian Thực')),
      body: Column(
        children: [
          Expanded(
            child: StreamBuilder<List<Map<String, dynamic>>>(
              stream: _messagesStream,
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return Center(child: Text('Lỗi: ${snapshot.error}'));
                }
                if (!snapshot.hasData) {
                  return const Center(child: CircularProgressIndicator());
                }

                final messages = snapshot.data!;
                return ListView.builder(
                  reverse: true,
                  itemCount: messages.length,
                  itemBuilder: (context, index) {
                    final message = messages[index];
                    return ListTile(
                      title: Text(message['content']),
                      subtitle: Text(message['created_at']),
                    );
                  },
                );
              },
            ),
          ),
          _MessageComposer(),
        ],
      ),
    );
  }

  Widget _MessageComposer() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      child: Row(
        children: [
          Expanded(
            child: TextField(
              controller: _textController,
              decoration: InputDecoration(
                hintText: 'Nhập tin nhắn của bạn...',
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(20),
                ),
              ),
            ),
          ),
          IconButton(
            icon: const Icon(Icons.send),
            onPressed: _sendMessage,
          ),
        ],
      ),
    );
  }
}

3. Hiểu Về Mã Nguồn

Hãy cùng phân tích các phần chính của widget ChatPage.

Luồng Tin Nhắn

dart Copy
final _messagesStream = Supabase.instance.client
    .from('messages')
    .stream(primaryKey: ['id']).order('created_at');
  • Supabase.instance.client.from('messages'): Điều này nhắm đến bảng messages trong cơ sở dữ liệu Supabase.
  • .stream(primaryKey: ['id']): Đây là chức năng kỳ diệu. Nó tạo một kết nối WebSocket đến Supabase và lắng nghe bất kỳ thay đổi nào (thêm, cập nhật, xóa) trong bảng messages. Khóa chính là cần thiết để Supabase theo dõi các thay đổi một cách hiệu quả.
  • .order('created_at'): Chúng ta sắp xếp các tin nhắn theo thời gian tạo của chúng để đảm bảo chúng được hiển thị theo thứ tự chính xác.

Widget StreamBuilder

StreamBuilder kết nối UI của chúng ta với _messagesStream.

dart Copy
StreamBuilder<List<Map<String, dynamic>>>(
  stream: _messagesStream,
  builder: (context, snapshot) {
    // ... xử lý trạng thái tải, lỗi và dữ liệu
  }
)
  • stream: Chúng ta truyền _messagesStream vào đây.
  • builder: Hàm này được gọi mỗi khi có một sự kiện mới đến trên luồng. Đối tượng snapshot chứa dữ liệu mới nhất.
  • Xử Lý Trạng Thái: Chúng ta kiểm tra snapshot.hasData để xem chúng ta có tin nhắn hay không. Nếu không, chúng ta hiển thị một chỉ báo tải. Nếu có lỗi, chúng ta hiển thị lỗi đó.
  • Hiển Thị Dữ Liệu: Khi có dữ liệu (snapshot.data!), chúng ta sử dụng ListView.builder để hiệu quả hiển thị danh sách các tin nhắn.

Gửi Tin Nhắn

Hàm _sendMessage xử lý logic gửi tin nhắn mới.

dart Copy
Future<void> _sendMessage() async {
  final content = _textController.text.trim();
  if (content.isEmpty) {
    return;
  }
  _textController.clear();

  final userId = Supabase.instance.client.auth.currentUser?.id;

  await Supabase.instance.client.from('messages').insert({
    'content': content,
    'user_id': userId,
  });
}
  • Chúng ta lấy văn bản từ _textController.
  • Thực hiện một thao tác thêm vào cơ sở dữ liệu trên bảng messages.
  • Sau khi hàng mới được thêm, tính năng Realtime của Supabase tự động phát hiện thay đổi này và phát sóng đến tất cả các khách hàng kết nối. Chúng ta không cần quản lý trạng thái thủ công (setState) cho danh sách tin nhắn!

Thực Hành Tốt Nhất

  • Quản Lý Người Dùng: Đảm bảo rằng bạn quản lý trạng thái người dùng và xác thực đúng cách để bảo vệ thông tin người dùng.
  • Tối Ưu Hóa Hiệu Năng: Sử dụng các chỉ số và tối ưu hóa luồng dữ liệu để cải thiện hiệu suất ứng dụng.

Những Cái Bẫy Thường Gặp

  • Không Bắt Lỗi: Đảm bảo xử lý các lỗi từ Supabase để tránh ứng dụng bị sập.
  • Quá Tải Dữ Liệu: Tránh tải quá nhiều dữ liệu một lúc, hãy phân trang hoặc tải theo yêu cầu.

Mẹo Hiệu Năng

  • Cải Thiện Tốc Độ Tải: Sử dụng bộ đệm cho các tin nhắn đã tải để giảm thời gian tải.
  • Sử Dụng Đa Luồng: Cân nhắc sử dụng đa luồng để xử lý các tác vụ nặng mà không làm ảnh hưởng đến trải nghiệm người dùng.

Kết Luận

Chúng ta đã thành công trong việc xây dựng một ứng dụng chat thời gian thực hoàn chỉnh sử dụng Flutter và Supabase. Chúng ta đã đi từ việc thiết lập dự án Supabase và bảng cơ sở dữ liệu đến việc xây dựng một UI Flutter hoàn chỉnh cho việc gửi và nhận tin nhắn theo thời gian thực. Sự kỳ diệu nằm ở tính năng luồng của Supabase và widget StreamBuilder của Flutter, điều này cùng nhau xử lý tất cả độ phức tạp của việc đồng bộ hóa dữ liệu thời gian thực.

Đây chỉ là khởi đầu. Bạn có thể mở rộng dự án này bằng cách triển khai một quy trình xác thực đầy đủ với Supabase Auth, thêm hồ sơ người dùng, hiển thị tên người dùng bên cạnh tin nhắn và kích hoạt Row Level Security cho một ứng dụng sẵn sàng đưa vào sản xuất. Sự kết hợp giữa Flutter và Supabase cung cấp một nền tảng mạnh mẽ và hiệu quả để xây dựng các ứng dụng hiện đại và có thể mở rộng.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào