Flutter Dev Podcast с CTO Meduza Борисом Горячевым: главное про приложение Meduzа и медиа-разработку

    Не каждый день выпадает шанс обсудить проект на Flutter с более 100 тысяч DAU в приложении. Новое приложение Meduza — как раз такой проект. На текущий момент — это самое популярное и обсуждаемое Flutter-приложение в российских сторах. Мы пригласили Бориса Горячева на Flutter Dev Podcast, чтобы выяснить почему команда решила уйти с натива на кроссплатформу и выбрала Flutter. Ниже мы собрали самое интересное: попытки подружить web с нативом, первый опыт кроссплатформы, особенности медиа-разработки, играх со шрифтами и сложностях работы с WebView / Backend Driven UI.



    «Идея, сайт, два приложения». Изначально на запуске Meduza в 2014-ом команда сделала ставку на удобное мобильное приложение, сайт был максимально прост. Как выяснилось позже, аудитория в тот момент ещё не была готова отказываться от новостей на web. Приложение набрало свою аудиторию, но в абсолютных числах уступало web (как моб. сайту, так и десктопу). Поэтому было принято решение улучшить техническое оснащение сайта.

    «Аудитория Meduza». Во внутренних метриках Meduza чётко разграничивают мобильных и десктоп пользователей сайта, а также юзеров приложения. Последних меньше всего, но их активность значительно выше, чем у всех остальных категорий. Это «ядро» аудитории Meduza.

    «Какой была первая версия приложения». Meduza запускалась буквально за пару месяцев. Ставку делали на красивый контент, который подгружался из браузера. Принцип создания медиа-приложений у всех одинаковый: нативным образом делается главная и другие раздающие страницы. Когда человек тапает на материал, открывается WebView, в котором показываются различные HTML, CSS и другие модули.

    «Изменился ли подход к разработке с течением времени». Нет, механика осталась прежней: сначала нативный вид, потом пользователь «проваливается» в WebView, в котором скроллит контент из браузера. У такого подхода есть свои недостатки. Когда контент контролируется браузером, приходится уделять много внимания общению между нативным кодом и web-формой. Например, чтобы нарисовать какой-то нативный компонент, который будет появляться с красивым эффектом и следовать за пользователем. На этом команда набила немало шишек.

    «Какие были проблемы во взаимодействии web с нативом». Web-разработчики и нативные разработчики говорят на разных языках. Во время разработки были проблемы с сериализацией, десериализацией, падала Java, потому что приходил какой-то не тот объект, не той структуры и т.д. Также были сложности со встраиванием элементов, зависимых от позиции скролла. Например, есть ограниченный по высоте WebView, который должен интегрироваться с куском натива. Приходится делать композицию из элемента, который сам по себе скроллится, потом перехватывает другой скролл и так далее.
    Когда я первый раз решил обсудить кроссплатформу с нативной командой, то встретил серьёзное сопротивление. Аргументы были стандартные: медленно, перфоманс плохой, нет поддержки такой-то фичи, которая нам сейчас и не нужна, но вдруг понадобится в будущем и так далее. Возможно, им хотелось изучать что-то новое или просто было комфортно с Java, Kotlin и т.д. В итоге через некоторое время нам пришлось попрощаться с нативной командой

    «Отрендерить контент Meduza нативно — всё равно, что повторить браузер так, чтобы он работал. Не самая простая задача». Команде удалось перевести часть материалов на нативный рендеринг. Например, в категории «Онлайны» материал заполняется редакцией в real-time: после отправки информация тут же рендерится на экране у читателя. Делали это всё на нативе. Например, на клиент приезжают динамические данные в разном формате. Клиент должен правильно их обработать и при этом не упасть. Проблем с согласованием всех нюансов было очень много — нужно было решать, как двигаться дальше.
    В тот момент, единственное, что я мог делать — это бэкендом обманывать приложения. Они показывали то, что уже перестало работать в нативе. Я такой: «Эй, сейчас мы туда напихаем костылей». В течение 2-х лет подкидывали в бэкенд всяких разных фичей, потому что не было желания обновлять приложения или найти нужную библиотеку.

    «Первый опыт кроссплатформы — React Native». У Meduza весь фронтенд написан на React. Первый мобильный прототип Борис написал на React Native за пару недель. Прототип работал нормально: показывал, рендерил, накладывал стили и т.д. Когда началась полноценная разработка приложения на React Native, вылезли новые проблемы. Например, JavaScript на Android работает не так, как на iOS. Есть валидный JavaScript, не ругается, но открываешь и видишь пустоту, а проблема оказывается в JSX. Также были проблемы с тегами, которые маппятся на контейнеры или текст, но закрываются как-то по-хитрому. По-началу казалось, что есть шанс починить все баги постфактум. Но чем дальше, тем больше разработка спотыкалась на мелочах.
    Тот факт, что нативные приложения работали эти 2 года, говорит о том, что написаны они были действительно хорошо. Они выдержали то, что мало какой софт может выдержать. 2 года без обновлений — это серьезный срок.

    «О Flutter узнали на Google I/O в 2018, но решили повременить». Пообщались с Эндрю Брогданом (Flutter). Meduza нужна была backend driven архитектура, чтобы API диктовало, что будет в UI. Ключевым моментом является наличие WebView, который интегрируется в дерево виджетов. Медиа-приложению необходимо показывать пользователю видео из соц. сетей и YouTube. К сожалению, на тот момент у Flutter не было поддержки WebView, и кроссплатформу отложили в долгий ящик. Нативные приложения прожили без поддержки почти 2 года. За это время было много различных правок на стороне бэкенда, но приложения при этом работали более-менее стабильно.
    Здравствуйте, я технический директор: «У нас есть два приложения, которыми пользуются люди? Отлично, они будут без поддержки». И в итоге они прожили без поддержки почти 2 года, но ими пользовались.

    «В декабре 2018 Google добавилиа библиотеку WebView во Flutter и началась разработка нового приложения Meduza». Она заняла почти год только потому, что команда несколько раз придумывала все приложение заново. Менялись экраны, концепция, проводились эксперименты. Flutter позволяет быстро делать интерфейсы, а благодаря hot reload дизайнеры могли оперативно комментировать изменения и вносить правки, много правок. На код приложения и бэкенд в общей сложности ушло не более 6 месяцев. При этом Борис не полностью был сосредоточен на Flutter, а отвлекался на разработку и запуск других проектов.
    Медиа — это такой тип активности человечества, где всё постоянно идёт не так. В мире постоянно что-то происходит: журналиста задерживают власти, меняется режим, начинается пандемия COVID-19 и т.п. Из-за этого тормозится развитие сервисов, переносятся совещания, страдает креатив.

    «Особенности разработки медиа-приложений в России». В нашей стране медиа-разработчиков очень мало, практически нет коллег по цеху, у которых можно было бы учиться и заимствовать лучшие практики. Программисты в этой области мыслят иначе. Там, где обычный специалист видит простой текст, медиа-программисты подмечают параграфы без типографирования, короткий минус вместо длинного тире, кавычки “лапки” вместо «елочек» и так далее. Такие детали отличают серьёзное издание от случайного сайта в интернете.
    Люди в медиа постоянно испытывают «усталость от повестки». От этого страдают, наверное, плюс-минус все люди в Meduza. Каждый день ты погружен в повестку, которая как правило не очень приятная. И в этих условиях происходят разные технические проблемы. То Google что-то поменял в индексах, отказало API Facebook или ВКонтакте, и они написали об этом в Change Log.

    «Возможность тонкой настройки шрифтов во Flutter очень пригодилась Meduza». Издание уже давно использует одну и туже пару шрифтов (Proxima Nova и PF Regal), но дизайнеры часто вносят в них небольшие изменения, которые на нативе было поддерживать очень тяжело. Разработчики не хотели трогать дефолтные компоненты, не видели изменений на ряде устройств и так далее.
    А. Лебедев новое приложение не оценил. Ну не понравилось и не понравилось. Комментарии валидные, на данный момент я ещё не смог поправить только один момент. В приложении сноски выделены синим цветом, а на CSS эффект обводки получался более интересный. В итоге пришли с дизайнером к компромиссному решению, которое Артемий не оценил. Ну не оценил, и не оценил.

    «Есть ли планы по масштабированию команды или одного Flutter-разработчика достаточно, чтобы писать медиа-приложения». Фреймворк действительно позволяет в соло написать проект. В случае с Meduza используется «блочная API», т.е. бэкенд диктует UI, что показывать. Суть в том, что любой материал Meduza представляет собой массив типизированных блоков. Как конструктор, из которого можно собрать параграфы, а внутри будет медиаплеер или что-то ещё. Плюс-минус любой блок может быть отображен в любой комплектации. На этом строится все приложение. В один момент можно взять и заменить что-то в любой точке приложения: отдать через бэкенд другой текст, отступы, цвета и т.д.

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

    В данном случае под форматами подразумеваются типы публикаций: новости, карточки, лонгриды и т.д. Все они активно используются редакцией. Главной болью ещё с натива была поддержка новых форматов в приложении. В старом использовался Webview, и постоянно были ситуации, когда редакция придумала классный формат, он был поддержан на web, но не поддерживался приложением. Приходилось ждать пока пользователи обновятся, и только после этого новые материалы начинали отображаться пользователям на смартфоне. Чтобы этого избежать, приложение должно идти впереди продуктового плана и поддерживать фичи, которых ещё нет в продакшене. Так люди успеют обновиться и все релизнется плюс-минус одновременно.

    «На Flutter получилось сделать процесс запуска новых фичей максимально безболезненным». В Meduza процесс проходит следующим образом. Бэкенд-программист пишет API, открывает на телефоне/симуляторе приложение. Дальше проверяет, что всё совпало, и идёт дальше делать приложение. Нет необходимости писать и настраивать новые виджеты. У такой схемы есть trade-off по перформансу и trade-off по читабельности кода. Когда логика одного компонента или блока становится достаточно серьёзной, из него просто делают виджет и блок становится новой сущностью. Например, подкаст-плеер был реализован именно так.

    «Какая часть приложения рендерится на Flutter, а какая через WebView». Большинство элементов рендерятся на Flutter: контейнеры, тексты, типографика. Исключение — embed-элементы (Twitter, YouTube, Facebook, TikTok) и игры, которые полностью сделаны через WebView. Например, тесты на Meduza или кастомные игры с машинками и т.п. Все они делаются с нуля. Тут важно знать, что WebView в Android на некоторых телефонах крашится, если WebView оказывается по высоте больше, чем ViewPort устройства. В перспективе уйти от WebView в пользу чего-то более «Flutter-нативного», тот же Twitter отрендерить по данным из JSON.

    «Какие планы на подкасты, как планируется улучшать». Изначально использовался плагин audioplayers, но потом было решено перейти на связку just_audio и audio_service. У плеера не было проблем с процессами, а во второй библиотеке поддерживались нотификации, нужные для подкастов. Например, ты включаешь что-то поиграть, а сверху вылезает модуль, где можно тапнуть на stop или seek audio и т.п. Сложнее всего было синхронизировать UI с чем-то, что происходит в отдельном треде, а также реализовать момент со шторкой и свайпами.

    «Приложение с точки зрения архитектуры или почему остановились на Provider». Для поддержания работы такой структуры требуется меньше действий: никаких бесконечных констант и функций, мутировавших глобальных state’ов и так далее. Из рассмотренных вариантов логика провайдера подошла Meduza больше всего. Cейчас в приложении их 8. Например, один отвечает за закладки пользователя, другой за синхронизацию истории чтений и подкасты. С его помощью запоминают позицию, на которой остановился слушатель. Так он может вернутся на другом устройстве и дослушать подкаст там. Ещё один провайдер отвечает за темы, подставляет где надо нужные цвета или отдаёт их куда надо в UI.

    «Из-за чего приложение показывает разный перфоманс и рендеринг UI на Android и iOS». Первая проблема крылась в initial fetching данных, который был реализован через Provider, но не был задействован Compute. Там довольно много операций: пойти за данными в API, забрать довольно большой JSON, обработать, преобразовать в данные и запихнуть результат в модели. Из-за чего на старте приложения наблюдаем просадку FPS.

    Другая проблема — интеграция рекламы, от которой происходит просадка FPS на iOS. На Android же рекламы просто нет. Когда выбирали библиотеки для приложения, остановились на официальной AdMob. Но, как оказалось, там нельзя интегрировать баннер в древо виджетов. Использовать баннеры поверх дерева виджетов или fullscreen рекламу разработчики не хотят, так как это сильно ударит по лояльности читателей.

    Команда Meduza обратилась к Google и попала на бета-тест, где уже тестируется нужный формат рекламы на iOS. В текущей версии, когда анимируется переход, подгружается AdMob реклама. По benchmark видно, что есть перформанс-просадка, но не везде и не всегда. Ждём фикс от Flutter-команды. Мораль простая: если что-то надо вынести из Main Isolate используйте Compute.

    В итоге, несмотря на все трудности, Flutter полностью оправдал ожидания команды Meduza. Ищите полную версию подкаста тут и задавайте вопросы в комментариях!
    Surf
    Компания

    Комментарии 6

      +2

      За логотип медузы в заголовке, об который постоянно спотыкается глаз, существует отдельный котёл. Я и так знаю что я читаю медузу, зачем мне об этом постоянно напоминать?
      Как и за яркую белую рекламу в тёмной теме.

        +2

        "Мораль простая: если что-то надо вынести из Main Isolate используйте Compute." — Мда… Сразу ощущается проффесионализм разработчика… А то что при каждом вызове создается новый изолят а это почти 2мб памяти + довольно приличное время на создание и запуск изолята, учесть то что надо вложится в интервал 16 мс чтоб не проседал фпс… Конечно юзайте всегда Compute… Дилетанты… Необходимо на старте создать пулл изолятов и с ними работать в процессе...

          0
          не покажете код, где это реализовано?
          0
          Не устаю радоваться токсичности разработчиков. За смысл коммента, впрочем, спасибо.
            0
            Согласен, с токсичностью перегнул…

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое