Как мы делали чаты на Kotlin Multiplatform

Недавно завершили большой этап работ по чатам на Kotlin Multiplatform.

Работы велись в рамках мобильного приложения. Если коротко, это — приложение сохраняет для вас важные контакты при посещении конференций. Вы находитесь в одном зале с участниками конференции, по GPS приложение определяет всех, кто был рядом с вами в радиусе 10 метров, и эти люди потом отображаются у вас в мобильном приложении.

Приложение мы делаем на Kotlin Multiplatform для ускорения разработки на 2 платформы: Андроид и iOS.

Какую задачу мы получили

Задача по чатам — один из этапов разработки. И заказчик хотел получить оценку по срокам и стоимости этой задачи перед принятием решения реализовывать этот функционал. Вообще, в нашей компании работа ведется по Time & Material, но в данном проекте чисто по Time & Material работать не получалось из-за особенностей заказчика. Проект нам интересен, поэтому приходится как-то подстраиваться под некий гибрид водопада и T&M.

Заказчик хотел, чтобы можно было переписываться с найденными контактами. Причем, чат нужен был внутри приложения.

Вот, собственно, дизайн того, что от нас ждали:

Макеты чатов

Макеты чатов

Вроде выглядит все просто: нужно иметь возможность написать кому-то из списка контактов и переписываться с разными людьми в отдельных чатах.

На сколько часов мы напроектировали

Проектирование провели основательно. С planning poker, всей командой: при участии и бэкенд, и мобильных разработчиков.

Оценка трудоемкости

Оценка трудоемкости

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

Знали бы, насколько мы окажемся оптимистичны, и сколько нас ждет трудностей впереди!  

(Спойлер — мы ошиблись в 3 раза)

Технологии

Обычно для реализации простейших запросов используется Http Rest механизм, с которым вы наверняка уже знакомы, но в случае реализации чата нам необходимо моментально (в live) получать/отправлять новые сообщения от собеседника. Для этого нам потребуется постоянное, двустороннее соединение между сервером и мобильным приложением (клиентом) этот механизм реализуется с использованием протокола WebSocket. 

Коротко про WebSocket:

1. Соединение осуществляется между клиентом и сервером посредством TCP handshake протокола.

2. Сервер периодически пингует клиента на проверку соединения, клиент отвечает — понг, если клиент не отвечает — соединение закрывается сервером.

Проектировать взаимодействие с веб-сокетом нам нужно было сразу на 2 платформы — мы же пишем мультиплатформенное решение. Только пуши остались для нативной реализации: отдельно на Андроиде и отдельно на iOS.

В приложении были использованы следующие технологии:

  • Ktor — библиотека, реализующая сетевой стек (HTTP, WebSocket) для различных платформ с поддержкой плагинов для расширения функциональности;

  • Kotlin Multiplatform — фреймворк, позволяющий разделять логику, написанную на Kotlin, между множеством платформ;

  • Jetpack Compose — UI фреймворк, изначально предназначенный для разработки под Android, но получивший реализации под другие платформы;

Для бэкенда выбрали микросервисную архитектуру, т.к. пожеланием заказчика была масштабируемость.

Технологии бэкенда:

  • .NET 7

  • Docker

  • RabbitMQ

  • Kubernetes

  • WebSocket

  • Elasticsearch

  • PostgreSQL

  • MongoDB

Что вошло в задачу

Ниже перечислю, что может потребоваться для реализации чатов в целом. Галочками отмечу, что реализовано в мобильном приложении Я вас видел:

  1. Статусы online/offline, последняя активность ✅

  2. Статусы прочтения сообщений ✅

  3. Групповые чаты

  4. Пуш уведомления ✅

  5. Два экрана — экран чата и экран списка чатов ✅

  6. На экране списка чатов последнее сообщение собеседников ✅

  7. Динамическое появление нового сообщения на экране чата и на экране списка чатов ✅

  8. Реализация пагинации в связке с динамическим появлением нового сообщения на экране списка чатов и на экране чата ✅

  9. Локальное хранение  сообщений

  10. Отправка смайликов ✅

  11. Отправка файлов 

  12. Отправка изображений 

  13. Отправка видео 

  14. Отправка ссылок ✅

  15. Возможность редактирования/удаления диалога/сообщения 

  16. Возможность переслать сообщения 

  17. Удаление диалога/сообщения 

  18. Отображение удаленных пользователей ✅

  19. Реакции на сообщения

Как шла работа

Провели оценку с проектированием, получили добро от заказчика и стартанули. Я перенесла все задачи в YouTrack вместе с оценками. Собрали команду из 4-х разработчиков: 1 бэкенд, 2 андроид и 1 iOS. Все ребята были не на полную загрузку (у них были еще другие проекты).

Бэкендер не мог сразу приступить к реализации спланированных сокет-методов (из-за занятости на других проектах). Поэтому мобильные разработчики занялись версткой экранов. Что мы сделали очень даже быстро, затратив даже меньше часов, чем планировали.

Дождались бэкендера, и началась разработка подключения к сокетам и реализация сокет-методов. Вот тут уже стало ясно, что процесс проектирования продолжается, и он продолжался буквально до самого завершения разработки.

Искали инструмент для ведения документации по сокет-методам, выбирали между Swagger или Postman, но тестировать в них сокеты все равно бы не получилось, поэтому остановились на Google Docs. 

Пример описания сокет-метода

Пример описания сокет-метода

Мы встречали одну за одной технические сложности. То, как ребята, с ними расправлялись, вызывало восхищение и одновременно сильные переживания. Несколько раз прямо запинались серьезно и надолго, натыкаясь на неожиданное поведение библиотек Андроида, либо Kotlin, либо Compose.

Дейлики проводили каждый день. Иногда обсуждения вариантов решения проблемы растягивались на несколько дней. Были споры, разные варианты решения, обсуждали и выбирали наиболее оптимальный путь.

Иногда казалось, что вот уже основное все работает, осталось чуть-чуть, но какая-то небольшая проблема заставляла перепроектировать механизм клиент-серверного обмена.

Одна из таких задач (казавшихся простыми, но занявших много времени) уже на последнем этапе была — пуш уведомления. Вернее, настройка их показа и не показа (когда мы находимся на экране чата). 

Пуш-уведомление

Пуш-уведомление

Все крайние точки, которые могут возникнуть у пользователя при пользовании чатами: пропал Интернет, переключился Интернет с вай-фай на мобильный, приложение ушло в трей, приложение выкинули из трея, произошел перелогин в приложение и т.п., — потребовали к себе  очень чуткого отношения и заставили нас несколько раз переписать очередь сообщений в веб-сокеты, и даже навигацию в приложении.

Когда стало ясно, что со сроками затягиваем конкретно, провела упорядочивание задач, выстроила отношения и связи дочерних задач с тремя главными родительскими: Чаты–бэкенд, Чаты-Андроид, Чаты-iOS. Благодаря этому, мы имеем сейчас возможность сравнить реальную картину трудозатрат с прогнозируемой.

Какие были трудности

Основной проблемой, повлиявшей на увеличение объема работ, было отсутствие готового решения по чатам для Kotlin Multiplatform. Его пришлось писать с нуля.

Отдельное место в трудностях реализации заняла пагинация сообщений и списка чатов. Как оказалось, Kotlin Multiplatform не поддерживал пагинацию, ее пришлось разрабатывать самостоятельно, причем это стало мультиплатформенным решением, которое работает на Андроид и iOS.

Помимо пагинации, вот еще сколько интересного пришлось встретить:

  • чтобы в случае пропадания соединения сообщения приходили в нужном порядке, пришлось реализовывать мультиплатформенную потоконезависимую очередь с приоритетом сообщений к сокетам

  • при переключении между вайфай и мобильным интернетом сокет отключался, пришлось  пошаманить с логикой переподключений

  • одна только схема экспоненциального retry для избежания зацикливания при открытии сессии подключения к сокету чего стоит!

  • пришлось повозиться с навигацией в Compose, которая оказалась сырая и принесла нам ряд проблем при попытке открыть чат с разных мест приложения

  • а открытие чатов с пуша, когда приложение закрыто, или в трее, или открыто, но не на чатах — вроде бы логично все должно открываться, но, как оказалось, каждую ситуацию нужно было программно реализовывать и досконально тестить

  • сделать так, чтобы пуши не приходили разлогиненному пользователю.

И еще очень-очень много всего интересного.

Что получилось

Чаты работают. Заказчик доволен. 

Реакция заказчика

Реакция заказчика

Небольшое видео работы чатов.

Сколько в итоге все заняло

В итоге, весь процесс разработки и тестирования занял 806 часов.

В среднем, мы ошиблись в оценке трудоемкости в 3 раза.

Тезисно про основные трудности:

  1. Отсутствие готового решения для чатов на Kotlin Multiplatform

  2. Отсутствие поддержки пагинации в Kotlin Multiplatform

  3. Сохранение стабильности подключения к сокетам при различных ситуациях (смена источника Интернета на телефоне, переключение между экранами в приложении, выход и возвращение в приложение и т.д.)

  4. Сырая навигация в Compose

  5. Реализация переходов в чаты с пуш-уведомлений с разных мест и состояний приложения.

На моей практике, это самая большая ошибка в оценке проекта. И да, я понимала, что задача будет нетривиальной, но рисков не заложила, т.к. нужна была минимальная оценка.

Большой ошибкой тут было не заложить время на общение команды. Те же самые дейлики, которые проходят каждый день, и занимают по 30–90 минут, съедают много времени, учитывая, что в них участвуют 4–5 человек. Теперь при оценке проектов я закладываю время на общение внутри команды.

Выводы

  1. Теперь мы имеем крутое решение по чатам на Kotlin Multiplatform, которого еще не было до этого (по крайней мере, мы не нашли в открытых источниках).

  2. Проект Я вас видел продолжается. Отдельная благодарность заказчику за терпение. Верим, что проект взлетит, а мы сделаем для него еще немало крутых фич.

  3. На будущее, в оценках трудозатрат: даже в минимальных оценках учитывать время на общение внутри команды.

  4. Если приходится подстраиваться под заказчика и идти на некий гибрид водопада и T&M, то нужно проводить дополнительные беседы с заказчиком, разъясняющие особенности разработки ПО, возможные подводные камни и сдвиг сроков. Желательно «на берегу», до решения о старте работ.

© Habrahabr.ru