Привет! На связи Никита Грибков, Flutter-разработчик AGIMA. В прошлом году я стал свидетелем жутких событий, которые разворачивались на одном из наших проектов. В сущности, жуткими они были только потому, что техзадание состояло из сложных и нестандартных задач — но всё-таки они изрядно потрепали нам нервы.
Времени на всё про всё, как водится, было по минимуму. Мы закатали рукава, вооружились всеми доступными инструментами — и начали подбирать решение для каждой проблемы. Ниже опишу, что представлял собой проект и какие именно задачи заставили нас поднапрячься.

Немного о проекте
Это было приложение для аграриев — то есть буквально для людей, которые работают в полях. Грубо говоря, оно помогало фермерам следить за состоянием угодий: например, проверять, что уже обработано от колорадского жука, а что пока не обработано. Были и другие функции, цель которых — упростить ведение сельского хозяйства. В общем, полезная штука для конкретной и очень понятной целевой аудитории.
Но поскольку пользоваться приложением должны были в довольно специфических условиях — в нем предусмотрели несколько любопытных функций. Например, оно должно было бесперебойно работать в офлайне — поля всё-таки бывают далековато от вышек мобильных операторов. Отдельные решения можно назвать и вовсе нестандартными для нас — и чтобы к ним адаптироваться, пришлось поковыряться.
В общем и целом я насчитал пять компонентов проекта, над которыми нам пришлось крепко поразмышлять:
-
Видеоплеер. Фоновое изображение на главном экране представляло собой видео. Бэкенд нам его присылал в 4K, и далеко не каждый телефон справлялся с обработкой такого тяжеловеса.
-
Модальные окна. Они были практически везде — клиент хотел, чтобы всё красиво поднималось и опускалось. Из-за этого видео в фоне (см. пункт 1) играло всегда. Так что модальные окна были большой проблемой.
-
Всплывающие подсказки. Мы видели приложения, в которых много всплывающих подсказок. Но тут их было больше, чем много — пользователям должно было быть максимально комфортно. К тому же затемнение экрана не всегда работало корректно — пришлось решать.
-
Архитектура. Проект разрабатывался в сжатые сроки, и поэтому в архитектуре учли не всё. С самого начала было сложно предусмотреть, что ждет приложение в будущем и нужно ли будет добавлять в него новые фичи.
-
Офлайн-режим. Сюда входит и кэширование страниц, и сохранение каких-то справочников в базе данных. Изначально мы выбрали для реализации Isar — NoSQL базу данных. Но позже поняли, что нам придется от него отказаться.
Причина некоторых проблем состояла в том, что еще на этапе проектирования было принято несколько неоптимальных решений. И меня привлекли к проекту как раз для того, чтобы я помог их исправить.
Видеоплеер
Причиной головной боли было огромное фоновое видео в 4К на главном экране. Оно было неподъемным для большинства устройств. Да, какой-нибудь флагман справился бы, но самые популярные гаджеты всё-таки подешевле, и мощности в них поменьше. А такое тяжелое видео съедало кучу ресурсов устройства.
Поскольку важен был еще и офлайн-режим, видео грузилось с бэкенда. И всё это было трудозатратно, особенно при первом запуске приложения. Более того, заказчик хотел, чтобы видео в будущем можно было запросто поменять, и это добавляло хлопот. Но к счастью, от последнего пункта клиента удалось отговорить.
Как мы решали всё остальное:
-
Первым делом мы перенесли видео на само устройство. Добавили его в ассеты и затем забирали уже из приложения. Так нам больше не приходилось тратить время на его загрузку из бэкэнда. Это ускорило старт приложения.
-
Сам файл по-прежнему был слишком большим. У нас было несколько идей, как это решить. Например, сначала попробовали оптимизировать плеер, но результат не понравился. Тогда решили, что на смартфонах будет достаточно Full HD и полностью отказались от 4K.
-
Но и это был еще не конец. Приложение представляло собой модалку — а значит, видео проигрывалось всё время, пока человек с ним работал. Это тоже ощутимо снижало производительность. Поэтому мы разработали специальный блок. Он получал пинг, когда пользователь уходил с главного экрана. После этого блок ставил видео на паузу. Но стоило человеку вернуться на главный экран — система снова запускала ролик.
В итоге мы добились успеха: видео стало легче, грузилось быстрее и не тормозило работу всего приложения.
Модальные окна
Заказчик хотел, чтобы везде были модальные окна. По его мнению, это делало приложение красивее. И признаюсь, мы все с этим внутренне согласились — выглядело действительно клево. Но проблема заключалось в том, что окна открывались поверх других экранов, а это тоже сказывалось на перформансе.
Представьте: у вас подгружается огромное видео, а заодно еще и модальное окошко. Причем то и другое открывается одновременно, ведь видео всё еще проигрывается. В общем, даже несмотря на то, что проблему с видео мы со временем устранили, сами по себе модальные окна существенно прибавили нам работы.
Какие проблемы и как решили:
-
Изначально в некоторых фрагментах кода были проблемы с оптимизацией. Особенно это было заметно в тех модальных окнах, которые содержали дропдауны, текстфилды и другие поля, подразумевающие динамически изменяемое состояние. При этом часть логики была в UI, а часть — в BloC. Мы решили всю логику завернуть под BloC, так как два источника истины явно приносили нам проблемы.
-
Модальные окна открывались одно поверх другого. Так происходило из-за отсутствия контроля над их состоянием. Если человек быстро нажимал на несколько кнопок, вызывающих окна, они могли открыться в неправильном порядке. Всё это ухудшало UX. Проблему решили просто: добавили ключи. Они позволили Flutter правильно идентифицировать окна и управлять их состоянием.
-
Также в приложении не было привычного менеджера модальных окон. Поэтому мы разработали свой. Он открывал и закрывал окна, опять же контролировал их порядок и управлял состоянием каждого из них.
Преимущества менеджера:
-
Помогает упростить код. Менеджер инкапсулирует логику работы с модальными окнами, что делает код чище и проще для поддержки.
-
Контролирует состояния. Менеджер гарантирует, что модальные окна не будут открываться друг поверх друга.
-
Добавляет гибкости. Добавлять новые модальные окна или менять их поведения стало проще.
Всплывающие подсказки
Подсказки удобны для пользователей — они показывают, на какую кнопку нажать и какой результат это принесет. Да и реализовать эту фичу, на первый взгляд, не то чтобы очень сложно: на экране появляется текст, ты тапаешь, возникает новая подсказка и т. д. Но, скажу я, реализация съела у нас много нервных клеток. Мы столкнулись с двумя большими проблемами.
Положение подсказок на экране. Подсказки должны были появляться рядом с определенными элементами интерфейса, чтобы указывать на них и объяснять их функцию. Но телефоны у всех разные: разная диагональ, разное разрешение. Поэтому подсказки могли выглядеть нормально на одном устройстве, но сильно съезжать на другом. Мы решили переделать эту функциональность полностью.
Для устранения этой проблемы был разработан механизм, который:
-
Определяет позицию элемента. Используя GlobalKey, мы получаем координаты элемента, на который должна указывать подсказка.
-
Адаптирует позицию подсказки. На основе полученных координат и размеров экрана подсказка автоматически корректирует свое положение, чтобы не выходить за границы экрана и не перекрывать важные элементы.
-
Учитывает изменения интерфейса. При повороте устройства или изменении контента позиция подсказки пересчитывается в реальном времени.
После этого подсказки начали работать корректно.
Таймер для подсказок. Нужно было, чтобы всплывающая подсказка и затемнение экрана совпадали по времени. Поэтому мы объединили эти процессы в одну анимацию, построенную на AnimatedOpacity и Tween. Этот подход добавил синхронности. А чтобы подсказка исчезала одновременно с затемнением, использовали Timer и Future. Также добавили автоматическую проверку: новая анимация не могла запуститься, пока не закончится старая. Это предотвратило баги
В итоге могу сказать, что даже простые элементы вроде подсказок иногда могут вызывать трудности.
Архитектура
Каждый разработчик привык к какому-то определённому состоянию архитектуры, и я получил проект без каких-либо явных паттернов. То есть первоначальная команда заложила более простую архитектуру. Но с развитием проекта приложение обрастало модулями и функциями. И в итоге получился эдакий монстр Франкенштейна, собранный из разных кусочков и лоскутов. Надо было эту проблему решить.
Но как это часто бывает, мы были сильно ограничены в сроках: не успевали продумать новую архитектуру, не успевали рефакторить какие-то виджеты. Поэтому увеличивался риск ошибок, а нас это раздражало. В какую-то секунду мы прошли через точку кипения и решили, что пора полностью отрефакторить и переработать архитектуру.
Мы пошли в сторону DDD — это Domain Driven Design Principle. Этот подход чаще используют в бэкенде, но нам он нравился, так как во многом напоминал чистую архитектуру. А поскольку у нас есть код-стайл, решили, что не будем изобретать велосипед и воспользуемся знакомым решением. Поэтому быстро перешли на рельсы DDD, почти полностью перенесли логику в состояния. Всё стало работать лучше.
Офлайн-режим
Кульминацией работы над приложением стала именно эта фича — она добавила седых волос всей команде. Для реализации офлайн-режима изначально был выбран Isar — NoSQL база данных от разработчиков Hive. Лично я долгое время сам агитировал за Isar. Мне нравилось, что там есть UI-интерфейс, что можно посмотреть в БД, что можно спокойно управлять данными. Всё это очень удобно.
Но на этом проекте я немного поменял мнение. Так как у приложения был офлайн-режим, на старте оно загружало большие объемы данных. И это не считая всё того же гигантского видео. Это создавало некоторые проблемы. В приложении добавлялись новые справочники, а вот документации на миграцию в Isar не было. Кроме того, в этой базе всплыли баги, которые вылезли на Android 32-ой архитектуры.
Мы не знали, как их исправить. Писали письма разработчикам Isar, смотрели, что пишут об этом в сообществах. Но в итоге махнули рукой: решили всё переписать на SQL. Выбрали Drift, так как уже имели опыт работы с ним. Взяли уже готовый интерфейс и добавили его в приложение. Вскоре поняли, что Drift отвечает нашим запросам.
А вот Isar больше использовать не будем — слишком много мороки.
Выводы
Проект, несмотря на свою понятность и прозрачность, оказался крепким орешком: тут и там всплывали неожиданные баги, а над отдельными требованиями заказчика пришлось поломать голову. Однако в итоге мы справились, вышли из этой борьбы победителями, зарелизили приложение со всеми доработками и исправлениями. И теперь оно успешно работает.
Нам же этот опыт помог сделать несколько важных выводов:
-
Видео в 4К для приложения на Flutter — это сложно реализуемое решение, от которого лучше сразу отговаривать заказчика. Ресурсов уйдет много, а выход найти будет непросто.
-
Время на архитектуру нужно находить даже тогда, когда кажется, что времени нет совсем. Это поможет избежать миллиона проблем в будущем, сэкономит деньги, время и нервы.
-
Isar — не лучшее решение, даже если поначалу кажется, что лучшее.
Ну и факультативно немного философии:
-
На сложных проектах мы набиваем шишки, но становимся сильнее как команда. Так что не нужно бояться неожиданных решений и нетривиальных задач. Всё к лучшему.
На этом всё. Если есть вопросы — готов ответить в комментариях. А также интересно почитать ваши истории о подобных проектах и подобных проблемах. С чем сталкивались? Как решали? Рассказывайте. Ну и подписывайтесь на наш канал про разработку — там не только про Flutter, но и про него тоже.
Что еще почитать
ссылка на оригинал статьи https://habr.com/ru/articles/892278/
Добавить комментарий