Інтеграція WebSocket-з'єднання для чату мобільного застосунку
WebSocket дає повнодуплексне з'єднання з мінімальними накладними витратами порівняно з polling — це правильний вибір для реального чату. Але мобільна специфіка додає шар складності: застосунок уходить у фон, мережа змінюється з Wi-Fi на LTE, користувач блокує екран. WebSocket-клієнт, який добре працює на десктопі, може втратити з'єднання через 30 секунд після уходу в фон на iOS через агресивне управління ресурсами.
Управління з'єднанням — основна складність
iOS. Background execution для WebSocket офіційно не підтримується. Якщо застосунку потрібно отримувати повідомлення у фоні — єдиний офіційний шлях це push-уведомлення (APNs). WebSocket залишається активним тільки поки застосунок на передньому плані. Спроби тримати з'єднання живим через URLSessionWebSocketTask з фоновим URLSessionConfiguration працюють нестабільно та порушують Guidelines.
На iOS використовуємо URLSessionWebSocketTask (iOS 13+):
class WebSocketManager {
private var webSocketTask: URLSessionWebSocketTask?
func connect() {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
webSocketTask = session.webSocketTask(with: URL(string: "wss://api.example.com/ws")!)
webSocketTask?.resume()
receiveMessage()
}
private func receiveMessage() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
self?.handleMessage(message)
self?.receiveMessage() // рекурсивно
case .failure(let error):
self?.scheduleReconnect()
}
}
}
}
Android. OkHttp WebSocket — де-факто стандарт. У фоні з'єднання можуть обривати JobScheduler або Doze Mode на Android 6+. Рішення: WorkManager для періодичної синхронізації + FCM push для доставки повідомлень у фон. WebSocket тримаємо живим тільки коли застосунок у foreground, опціонально — ForegroundService з уведомленням.
val client = OkHttpClient.Builder()
.pingInterval(30, TimeUnit.SECONDS) // heartbeat
.build()
val request = Request.Builder().url("wss://api.example.com/ws").build()
val ws = client.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
// обробляємо повідомлення
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
scheduleReconnect()
}
})
pingInterval(30) важний — без heartbeat з'єднання розривається проміжними прокси після 60–90 секунд тишини.
Переподключення з експоненціальним backoff
Переподключення при розриві — обов'язкова логіка. Простий retry через 1 секунду створює шторм запитів при падінні сервера. Правильний підхід — exponential backoff з jitter:
private var reconnectDelay = 1000L
fun scheduleReconnect() {
viewModelScope.launch {
delay(reconnectDelay + Random.nextLong(500))
reconnectDelay = minOf(reconnectDelay * 2, 30_000L)
connect()
}
}
fun onConnected() {
reconnectDelay = 1000L
}
Flutter: пакет web_socket_channel — обёртка над нативними реалізаціями. Для продакшн-рівня рекомендуємо stomp_dart_client якщо сервер використовує STOMP, або кастомний manager з тими ж принципами reconnect.
Аутентифікація
WebSocket-з'єднання аутентифікується один раз при установці — через Authorization заголовок у handshake або першим повідомленням після підключення (auth frame). JWT-токен може протухнути за час сесії — потрібна логіка оновлення токена та переподключення.
Що входить в роботу
Реалізуємо WebSocket-клієнт під платформу з reconnect-логікою, heartbeat, обробкою зміни мережі (використовуємо NetworkCallback на Android, NWPathMonitor на iOS). Якщо потрібен fallback на push — інтегруємо APNs/FCM для доставки повідомлень у фоні.
Строка: 4–8 днів для повноцінної реалізації з урахуванням edge-케이сів по мережевим умовам.







