О проекте

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

Компания Netgineers GmbH обратилась к нам, когда продукт был уже определён, а бэкенд — в работе. Требовалась мобильная часть: одна кодовая база на Flutter для iOS и Android — для продукта с необычно широкой для первого релиза поверхностью.

Онбординг

Онбординг

Запрос геопозиции

Запрос геопозиции

Список чатов

Список чатов

Лента канала

Лента канала

Информация о канале

Информация о канале

Чат с каналом

Чат с каналом

Новый пост

Новый пост

Задача

Две вещи делали этот проект сложнее обычной разработки.

Масштаб поверхности экранов

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

Реальное время везде

Чат — очевидное место, где появляются -соединения, но в Jepta они им не ограничены. Пришедший пост в канале, изменившийся счётчик комментариев, бейдж непрочитанного на вкладке чатов, отметка о прочтении — всё это работает через одно и то же постоянное соединение, в том числе на экранах, на которые пользователь сейчас не смотрит. Решать это отдельно на каждом экране означало бы по соединению на экран и состояние, которое расходится с самим собой при первом же переключении вкладки.

Наш подход

Мы рассматривали обе проблемы как одну: сначала построить общие части, а экраны сделать тонкими.

Один слой реального времени и много потребителей

WebSocket-соединение живёт под интерфейсом как единый клиент, который сам управляет своим жизненным циклом: подключение, аутентификация, heartbeat, переподключение с backoff, повторная подписка. Экраны не открывают сокеты — они подписываются на типизированные потоки событий и получают обновления. Сообщения, отправленные при отсутствии связи, ставятся в очередь и досылаются при переподключении, а не теряются; статус доставки (отправлено, доставлено, прочитано) — часть модели сообщения, а не догадка на основе того, что успело прийти. Поскольку слой общий, сообщение в открытом чате и увеличение бейджа на вкладке, куда пользователь не смотрит, — это одно и то же событие, обработанное один раз.

Скелет экрана вместо ручной сборки каждого

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

Геопозиция как полноценные входные данные

Близость — не фильтр, прикрученный к ленте, а организующий принцип продукта, поэтому слой геопозиции должен был быть надёжным: сценарии выдачи разрешений, корректно работающие при отказе, закешированные координаты, чтобы лента не была пустой, пока определяется местоположение, и расстояние, показанное рядом с самим контентом («в 5 км от вас» в шапке канала), чтобы ранжирование читалось, а не выглядело загадкой.

Результат

Jepta вышла на iOS и Android из одной кодовой базы на Flutter в отведённые шесть месяцев и с полным набором функций: чаты, каналы, посты, комментарии и лента с учётом геопозиции — в первом релизе, а не по частям в нескольких.

Дольше дедлайна прожила архитектура. Поскольку слой реального времени, навигационный граф и подход к состоянию были построены один раз и переиспользовались, добавление экрана после релиза осталось небольшим изменением, а не новой интеграцией с сокетом, роутером и кешем. Для продукта, чей роадмап — «больше поверхностей», именно эта разница накапливается.

Больше всего меня беспокоили шесть месяцев на приложение такого охвата. Jepta — это соседские чаты, каналы бизнеса, посты, комментарии и лента, которая перестраивается вокруг того места, где вы сейчас находитесь: длинный список экранов, фиксированная дата и трезвое ожидание, что половина уедет во второй релиз.

Команда Nerdy Production сначала построила общий фундамент — один слой реального времени, одну модель навигации, — и после этого каждый новый экран стал дешёвым. Мы вышли на iOS и Android с полным набором функций, чат надёжно работает с первого дня, а дорабатывать приложение с тех пор — небольшая задача, а не проект. Мы получили тот продукт, который заказывали, и к той дате, к которой заказывали.

Evgeny Syrtsov
Evgeny Syrtsov
Генеральный директор, Netgineers GmbH

FAQ

Jepta — приложение для гиперлокального общения в немецких районах. Оно организует людей по близости, а не по глобальному графу подписок: чаты, привязанные к адресу или дому, групповые чаты для тех, кто живёт по одному адресу, и каналы, которые ведёт локальный бизнес — например, пекарня или мастерская. Чаты, каналы и посты, которые видит пользователь, ранжируются по расстоянию от его текущего местоположения.
Сначала построить общие части, а экраны оставить тонкими. В Jepta это означало один навигационный граф с типизированными маршрутами, один подход к управлению состоянием, применённый одинаково в каждой фиче, и общий набор состояний: список, загрузка, пустой экран, ошибка. После этого новые экраны в основном собираются из готового, а не превращаются в новую интеграцию — именно это делает объём выполнимым в срок и не даёт приложению выглядеть как двадцать экранов от разных рук.
Держать одно соединение под интерфейсом — как единый клиент, который сам управляет своим жизненным циклом: подключение, аутентификация, heartbeat, переподключение с backoff, повторная подписка. Экраны не открывают собственных сокетов, а подписываются на типизированные потоки событий и получают обновления. Тогда сообщение в открытом чате и растущий бейдж непрочитанного на вкладке, куда никто не смотрит, — одно и то же событие, обработанное один раз, вместо соединения на каждый экран и состояния, которое расходится с самим собой.
Они ставятся в очередь и досылаются при переподключении, а не теряются. Статус доставки — отправлено, доставлено, прочитано — хранится как часть модели сообщения, а не выводится из того, что успело прийти, поэтому интерфейс показывает реальный статус сообщения, а не догадку о нём.
Jepta вышла на iOS и Android из одной кодовой базы на Flutter за шесть месяцев: чаты, каналы, посты, комментарии и лента с учётом геопозиции — всё в первом релизе, а не по частям в нескольких. Срок выдержали за счёт того, что слой реального времени, навигационный граф и подход к состоянию были построены один раз и переиспользовались на всех экранах.
Да, и в Jepta близость — организующий принцип продукта, а не фильтр поверх ленты. Это предъявляет требования к слою геопозиции: сценарии выдачи разрешений, корректно работающие при отказе, закешированные координаты, чтобы лента не была пустой, пока определяется местоположение, и расстояние, показанное рядом с тем контентом, к которому оно относится, — чтобы ранжирование читалось, а не выглядело загадкой.

Jepta сделана в рамках нашей услуги разработки приложений на Flutter — той же практики, что стоит за другими нашими проектами с реальным временем.