Android Websocket: Nâng cấp Client với TypeAdapterFactory (Phần 3)
Chào mừng các bạn quay trở lại! Ở Phần 1 và Phần 2, chúng ta đã xây dựng được một ứng dụng Chat có khả năng phân loại tin nhắn bằng Strategy Pattern ở phía Server. Tuy nhiên, nếu phía Client (Android) chúng ta vẫn chỉ dùng một WebSocketListener đơn giản và gọi trực tiếp vào UI, code sẽ sớm trở nên "spaghetti" khi các tính năng như gửi ảnh, thông báo hệ thống, hay quản lý phòng chat xuất hiện.
Trong Phần 3 này, mình sẽ hướng dẫn các bạn cách đưa ứng dụng Chat vào Clean Architecture kết hợp với Reactive Programming (Flow/Coroutines) để tạo ra một hệ thống client chuyên nghiệp, ổn định và dễ mở rộng.
1. Sơ đồ kiến trúc (Client Architecture)
Để đảm bảo tính tách biệt (Separation of Concerns), chúng ta sẽ chia hệ thống Chat thành 3 lớp chính:
- Data Layer (
ChatWebSocketUtil): Quản lý kết nối raw (OkHttp), cấu hình Gson vớiRuntimeTypeAdapterFactoryvà phát đi các sự kiện (Events) quaSharedFlow. - Domain Layer (
ChatRepository): Đóng vai trò là "cầu nối" nghiệp vụ. Nó ẩn đi chi tiết của WebSocket và cung cấp các phương thức sạch sẽ nhưsendMessage(),joinRoom(). - Presentation Layer (ViewModel & UI): ViewModel lắng nghe các sự kiện từ Repository, cập nhật trạng thái UI (
StateFlow) và hiển thị danh sách tin nhắn.
2. Model đa hình với RuntimeTypeAdapterFactory
Trong một ứng dụng Chat thực tế, tin nhắn không chỉ có text. Chúng ta có thể có tin nhắn hình ảnh, tin nhắn hệ thống (User Joined), v.v. Hãy dùng bí kíp của Gson để tự động map JSON vào Class tương ứng.
Bước 1: Định nghĩa Sealed Class cho Chat Message
sealed class ChatMessage
data class TextMessage(val sender: String, val content: String) : ChatMessage()
data class ImageMessage(val sender: String, val imageUrl: String) : ChatMessage()
data class SystemMessage(val content: String) : ChatMessage()
Bước 2: Cấu hình Gson trong WebSocket Utility
private val typeAdapterFactory = RuntimeTypeAdapterFactory.of(ChatMessage::class.java, "type")
.registerSubtype(TextMessage::class.java, "chat_text")
.registerSubtype(ImageMessage::class.java, "chat_image")
.registerSubtype(SystemMessage::class.java, "system_notif")
private val gson = GsonBuilder()
.registerTypeAdapterFactory(typeAdapterFactory)
.create()
Bây giờ, khi nhận được JSON {"type": "chat_image", "sender": "Hieu", "imageUrl": "..."}, Gson sẽ tự động trả về một object kiểu ImageMessage.
3. Quản lý luồng tin nhắn bằng SharedFlow
Trong Android, thay vì dùng Callback, hãy dùng SharedFlow để phát đi các sự kiện WebSocket. Nó cực kỳ mạnh mẽ cho dữ liệu thời gian thực.
object ChatWebSocketUtil {
private val _events = MutableSharedFlow<ChatMessage>(
replay = 0,
extraBufferCapacity = 64, // Buffer để không bị mất tin nhắn khi chat đến quá nhanh
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events = _events.asSharedFlow()
override fun onMessage(webSocket: WebSocket, text: String) {
val message = gson.fromJson(text, ChatMessage::class.java)
scope.launch { _events.emit(message) }
}
}
4. ViewModel: Xử lý Reactive chuẩn chỉnh
ViewModel sẽ lắng nghe (collect) các tin nhắn và cập nhật vào danh sách hiển thị.
Lưu ý quan trọng: collect vs collectLatest
collectLatest: Thường dùng cho trạng thái UI (ví dụ: hiển thị số người đang online). Nếu có dữ liệu mới đến, dữ liệu cũ đang xử lý sẽ bị hủy.collect: Dùng cho danh sách tin nhắn chat. Bạn không được phép bỏ lỡ bất kỳ tin nhắn nào, vì vậy hãy dùngcollectđể đảm bảo mọi tin nhắn đều được đưa vào danh sách.
class ChatViewModel(private val repository: ChatRepository) : ViewModel() {
private val _messages = MutableStateFlow<List<ChatMessage>>(emptyList())
val messages = _messages.asStateFlow()
init {
viewModelScope.launch {
repository.events.collect { newMessage -> // Dùng collect để không mất tin nhắn
_messages.update { currentList -> currentList + newMessage }
}
}
}
}
5. Cơ chế tự động kết nối lại (Auto Reconnect)
Một ứng dụng Chat sẽ rất tệ nếu người dùng phải mở lại app mỗi khi mạng chập chờn. Hãy sử dụng cơ chế "Exponential Backoff":
- Lần đầu mất kết nối: Đợi 1s rồi thử lại.
- Lần hai: Đợi 2s.
- Lần ba: Đợi 4s... Điều này giúp giảm tải cho Server khi có hàng nghìn người cùng mất mạng một lúc.
6. Tổng kết
Bằng việc áp dụng Architecture chuyên nghiệp cho ứng dụng Chat:
- Code sạch sẽ: Logic truyền nhận tách biệt hoàn toàn với giao diện.
- Dễ mở rộng: Muốn thêm tính năng "Gửi Sticker"? Chỉ cần tạo thêm Class
StickerMessagevà đăng ký vào Factory. - Trải nghiệm mượt mà: Nhờ vào Coroutines và Flow, ứng dụng xử lý tin nhắn real-time mà không gây giật lag UI.
Hy vọng qua 3 phần của chuỗi bài viết, các bạn đã nắm vững cách xây dựng một hệ thống WebSocket từ Server đến Client chuẩn "pro".
Chúc các bạn thành công!
All rights reserved