Реализация чата на Flutter при помощи вебсокетов

от автора

Всем привет, на связи PurplePlane! В этой статья мы хотели бы рассмотреть реализацию простого чата на языке Dart, используя протокол websocket.

Для чего еще используются сокеты? Вебсокеты могут использоваться в клиент-серверных приложениях, где необходимо создать двухстороннюю связь, чтобы серверная сторона могла инициировать процессы в клиентском приложении. Например, отображать уведомление в клиентском приложении при возникновении событии на сервере, или в случае, когда необходимо в режиме реального времени обновлять данные в клиентском приложении. Это можно использовать, чтобы отображать на карте местоположение и статус водителя. В качестве еще одного применения вебсокета — можно привести настройку из одного клиентского приложения (используемого родителем) через связь по сокету с сервером другого приложения (используемого ребенком). В этих приложениях, реализованных нашей компанией, успешность бизнес-процесса выстраивалась на последовательной корректной работе двух сокетов (клиент (родительское приложение) — сервер и клиент (используемый ребенком) — сервер).

Рассмотрим пример использования вебсокета при реализации простого чата с помощью библиотеки web_socket_channel.


Напишем класс для управления сокетом нашего чата. Он будет содержать объект сокета из библиотеки, подписку на события из этого сокета и комплитер для идентификации состояния процесса подключения. При старте приложения создадим объект этого класса.

class SocketApi {   SocketApi();    IOWebSocketChannel? _socket;   StreamSubscription? _socketSubscription;   Completer<bool> _connecting = Completer<bool>(); }

Если приложение требует авторизации, то соединение по сокету устанавливаем только в авторизованном состояниии. Для этого делаем в классе геттер и сеттер id профиля пользователя и при инициализации соединения передаем в запросе токен пользователя.

int? _profileId;  Future<void> setProfile({required final int profileId}) async {     _profileId = profileId;     await _connect();   }    void removeProfile() {     _profileId = null;     _close();   }  Future<void> _connect() async {     await _close();     final prefs = await SharedPreferences.getInstance();     final token = prefs.getString('TOKEN');     assert(_profileId != null);     final wsUrl = Uri.parse('ws://my-base-url/ws/chat/$_profileId/?token=$token');       try {         _socket = IOWebSocketChannel.connect(           wsUrl,           pingInterval: const Duration(seconds: 5),         );         _socketSubscription = _socket!.stream.listen(           _onMessage,           onDone: _onDone,           onError: (Object err, StackTrace stackTrace) {             print('Listen err $err, $stackTrace');           },         );         _connecting.complete(true);         debugPrint('connected');       } catch (_) {         debugPrint('cant connected');       }   }

Как видно из кода выше, мы создаем подписку на стрим и вешаем обработчики на все события и ошибки.

Напишем метод, который будет закрывать сокет при выходе пользователя из профиля.

Future<void> _close() async {     _connecting = Completer<bool>();     await _socketSubscription?.cancel();     _socketSubscription = null;     try {       await _socket?.sink.close(status.normalClosure);     } catch (_) {       debugPrint('Socket already closed.');     }      if (_socket != null) {       print(['disconnected', _socket?.closeCode, _socket?.closeReason]);     }     _socket = null;   }

Реализуем обработчики событий в сокете.

Future<void> _onDone() async {     await _close();     await Future<void>.delayed(const Duration(seconds: 1), () async {       await _connect();     }); }  void _onMessage(dynamic message) {     debugPrint('✅ RECEIVED: $message');     final msg = jsonDecode(message.toString()) as Map<String, dynamic>;     final chatJson = msg['chat'] as Map<String, dynamic>;     final event = msg['type'] as String;     if (event == 'write_message') {       // Реализуем обработчики различных типов событий чата     } else if (event == 'read_messages') {       // то же     } }

Реализуем метод для отправки сообщений в сокет:

Future<void> _send(     String cmd,     int chatId,     String? message, {     Map<String, dynamic>? data,   }) async {     if (_socket == null) {       await _connect();     }     final connectionResult = await _connecting.future;     if (connectionResult != true) {       return;     }     final d = <String, dynamic>{       'type': cmd,       'chat_id': chatId,       'message': message,     };     d.removeWhere((key, value) => value == null);     final json = jsonEncode(d);     debugPrint('? SEND: $d');     _socket!.sink.add(json);   }

Поле «cmd» в этом методе отвечает за тип отправляемого сообщения.

Реализуем примеры методов для отправки событий различного типа в сокет.

Future<void> sendTextMessage({   required final String text,   required final int chatId, }) async {   await _send('write_message', chatId, text); }  Future<void> setMessagesRead({required final int chatId}) async {   await _send('read_message', chatId, null); }

В итоге мы получили класс, который позволит реализовать работу простого чата с помощью вебсокета.


ссылка на оригинал статьи https://habr.com/ru/articles/792374/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *