При разработке мобильного приложения iOS или Android рано или поздно может встать вопрос: «Реализовать фичу на WebView или же нативно?». В некоторых случаях ответ лежит на поверхности, но, к сожалению, так бывает не всегда. А если очень велик соблазн предоставить пользователям новый функционал поскорее — это может склонить к неправильному решению, с которым впоследствии предстоит что-то сделать. Сегодня мы хотим поделиться с вами тем, какую стратегию мы выбрали в Циан для себя и как к ней пришли. Посмотрим, где же мы поставили запятую :) У Циан есть мобильные приложения iOS, Android, ну и Web-сайт с адаптированной вёрсткой под разные типы устройств. У нас довольно большой департамент разработки, и он разделен на продуктовые направления, каждое из которых отвечает за свой функционал. Продуктовые направления максимально автономны и свободны в способах достижения своих бизнес-целей, в принятии решений, в выборе инструментов. Но объединяет их одно — желание максимально быстро проверять свои гипотезы и максимальной скорости разработки фичей. В погоне за этим команды стали чаще прибегать к использованию WebView, особенно в рамках MVP-версий фичи. Порой возникали случаи, когда это промежуточное тестовое решение в нашем проекте несколько задерживалось :) Стало ясно, что помимо общего желания скорости необходимо, чтобы у всех было общее понимание того, в каких случаях можно использовать WebView, а в каких лучше не стоит. И мы выработали для себя свод рекомендаций, единый для всех паттерн поведения, чтобы каждая автономная продуктовая вертикаль могла принять взвешенное решение по использованию WebView для своего кейса. Наши рекомендации нисколько не претендуют на истину, мы их делали прежде всего для себя, исходя из наших условий: наша структура, требования бизнеса, наши проекты. Но вполне возможно, эти выработанные тезисы помогут вам выбрать технологии для фичи в вашем проекте. Если вы почерпнёте что-то новое для себя, что поможет добавить деталей вашей собственной стратегии использования WebView, мы будем особенно рады! ) На каждой из платформ у пользователя есть свои привычки и паттерны, поэтому, например, пользователю Android потребуется время, чтобы привыкнуть к iOS, и наоборот. Исходя из этого для нас важно во всех флоу максимально сохранять привычные пользователям каждой платформы основные UX-паттерны, такие как навигация, и поведения, поэтому в нашем случае WebView при использовании должен им соответствовать. А теперь давайте разберёмся, к чему мы пришли. Поехали! Прежде всего мы собрали плюсы и минусы использования WebView, чтобы на их основании выработать обоснованные рекомендации. Часть плюсов и минусов вызваны особенностью этой технологии, а часть связаны с нашим положением вещей. Начнём с плюсов, с ними проще разобраться. Здесь всё понятно, при создании экрана на WebView не нужно с нуля реализовывать экран на всех трёх платформах Android, iOS и Web. В этом разрезе сокращение происходит за счёт переиспользования единой логики мобильного сайта в мобильных приложениях. Конечно, прирост в скорости значительный, но он всё же будет не в 3 раза, как могло бы показаться, потому что всё равно требуется время на адаптацию вёрстки под платформы и на поддержку со стороны натива. Давайте рассмотрим случай, когда речь не про разработку полностью нового экрана, а про изменения в работе существующей фичи, реализованной на WebView. В этом случае очень велика вероятность, что для раскатки даже не потребуется обновления версии приложения, и пользователи старых версий приложения получат доступ к последним изменениям экрана. Причём произойти это может одновременно для всех платформ. Это актуально и для правок багов, ведь пользователи быстрее получат фиксы. У нас есть продуктовые команды, у которых нет нативных мобильных разработчиков iOS и Android. WebView даёт возможность таким командам внедрять фичи в мобильные приложения. Однако здесь есть нюанс, что поддержка WebView всё равно требуется со стороны нативной разработки, как минимум в реализации переходов и передачи данных. Кроме того, таким командам приходится помнить, что они могут загнать самих себя в рамки, так как область применения WebView ограничена, но об этом ниже. Вот мы и подобрались к минусам. Часть из них можно устранить или минимизировать, однако адаптировать WebView под 2 другие нативные платформы, которые сами по себе друг от друга заметно отличаются — задача очень дорогая, и мы всё равно будем сталкиваться с ограничениями и артефактами WebView, которых в принципе не может быть в нативе. В оптимизацию WebView тоже нельзя без конца упарываться, поскольку мы всё же хотели достичь прироста в скорости :)  Чисто технически WebView может отображать веб-страницы как с удаленного сайта, так и те, что уже храняться локально, например, в ресурсах приложения. Но такой способ мы не используем, так как в этом случае мы потеряем один их самых важных для нас плюсов WebView 2. Синхронный Update на пользователей. Уже не будет той гибкости, мы снова упремся в релизный цикл. А для нас это означает: чтобы отобразить UI, требуется получить данные из сети. Понятно, что приложение у нас клиент-серверное, что большая часть нативных экранов всё равно не автономны без сети. Несмотря на это, есть много примеров, когда нативному экрану не требуется никакой загрузки данных. В этих случаях пользователь не всегда сможет довести свой флоу до конца, но он сможет понять, правильно ли он вообще сюда перешёл, заполнить свои данные до появления интернета, не потеряв их. Экран авторизации — пример такого экрана. C WebView мы в лучшем случае сможем показать нативную ошибку без загрузки минимальных данных. В принципе можно было бы подумать в сторону дополнительного инструмента кэширования, чтобы обеспечить частичную поддержку офлайн-режима и для WebView. Но полностью достичь нативных возможностей всё равно не выйдет. Чтобы закэшировать что-то ненужное, нужно сначала загрузить что-то ненужное :) У нас на проекте есть сквозные данные, которые не привязаны к какому-либо конкретному User Flow и могут использоваться несколькими несвязанными между собой экранами. Такие данные сохраняются локально на устройстве и обновляются лишь время от времени, вплоть до раза в неделю. У WebView нет доступа к таким данным. Технически есть возможность прокидывать данные между WebView и нативной частью с целью сохранения и загрузки, однако это требует реализации на нативной стороне, что явно снижает профит от его использования в тех кейсах, где необходима подобная работа с данными. Что касается хранилища самого WebView, то оно во власти операционной системы, логика его очистки различается в разных версиях, зависит от многих факторов, в том числе от свободного объема дискового пространства, времени  последнего взаимодействия пользователя и т. п. Повлиять на это нет возможности. Получается, что WebView своими внутренними средствами не может гарантировать хранение важных данных. Экран в WebView долго загружает свой контент. Как минимум при первом переходе на него требуется гораздо больше времени, чем для нативного, и при слабом интернете разница только увеличивается. Очевидно, это накладывает дополнительные неудобства для пользователя. Помимо скорости, не стоит также забывать про объём передаваемой информации, как правило, WebView значительно прожорливей до пользовательского трафика. Этот фактор накладывает дополнительные ограничения в решении проблем с навигацией, мы не можем на каждом этапе флоу создавать новый WebView. У нас в проекте используется дизайн-система. Получается, на разных платформах: iOS, Android и Web, разная реализация. Фича на WebView будет использовать реализацию дизайн-системы для веба, а не нативную (iOS или Android), и она всегда будет отличаться от нативной даже в версиях одинаковой актуальности. Не будем сейчас глубоко вдаваться в детали нашей дизайн-системы, это не тема данной статьи. Отметим только, что у нас нет цели полностью унифицировать её реализации на разных платформах. Одна из причин в том, что в разных платформах разные UI/UX-паттерны, которые формируют у пользователей свои привычки, что тоже не хотелось бы нарушать. Работу с этим минусом сильно усложняет пункт из плюсов: «2. Синхронный Update на пользователей». Вот ведь ирония! ) Получается, если фича внутри WebView будет одновременно обновляться без релиза приложения, изменения будут доступны и пользователям старых версий приложений. Нативный UI в них будет «законсервирован», использовать версию дизайн-системы, актуальную на момент релиза, в то время как для версии внутри WebView могут произойти сильные изменения, совершенно неконтролируемые с нативной стороны. Различия в дизайне экранов будут чувствоваться и негативно влиять на общее впечатление от продукта. С другой стороны, чем больше ресурсов вкладывать в поддержание консистентности, тем меньше будет профита в WebView в вопросе сокращения затрат на разработку. Можно несколько загримировать WebView под натив, но полностью всё же не получится. Всё равно останутся артефакты WebView, приводящие к багам, которых в нативных экранах не может быть в принципе. В WebView отсутствует нативная плавность взаимодействия с интерфейсом экрана. Это касается и плавности скролла с анимациями, на нативе добиться 60 FPS намного проще, к тому же есть немало устройств с частотой обновления экрана 120 Гц, особенно Android. Кроме того, отличается взаимодействие с его интерактивными элементами, что приводит к непривычному в контексте нативного приложения UX для пользователей. К примерам такого поведения внутри WebView можно добавить выделение текста с появляющимся контекстным меню: Это просто пример, конкретно этот кейс может вылечить разработчик фичи, локальное решение довольно простое, но тогда об этом кейсе нужно помнить каждому фронтовому разработчику. Системное же решение надо прорабатывать. При использовании WebView навигация тоже накладывает свои трудности, особенно если фича WebView содержит переходы. В этом случае полностью не получится повторить нативный UX навигации, где кнопка «Назад» в NavBar ведёт к предыдущему экрану, как и жест возврата на предыдущий экран. Это ещё не все нативные возможности, скажем, в iOS есть нативное меню, которое всплывает при длительном нажатии на кнопку «Назад»: оно позволяет увидеть весь стек предыдущих экранов с возвратом к любому из них: Помимо проблем с интеграцией WebView в нативную навигацию, имеется чисто фронтовая проблема построения навигации внутри WebView, если в ней требуются переходы. Универсального решения навигации на вебе, подходящего для наших мобильных приложений, нет. Нюансов становится больше, когда требуется кастомизация навигации, даже самая простая, как, например, запрет пользователю уйти из целевой фичи. Порой это может приводить к очень спорным решениям, ломающим пользовательские шаблоны: Здесь мы видим кнопку в нативном навбаре, закрывающую весь флоу WebView, и одновременно кнопку «Назад» внутри WebView, которая возвращает на предыдущий шаг внутри неё. Как нативный блок нельзя встроить внутрь экрана на WebView, так и WebView не стоит встраивать в нативный экран. WebView — тяжёлый для отрисовки элемент, и в случае отдельного встроенного блока бьёт по производительности всего экрана. Когда это блок внутри нативного экрана, куда сильнее бросаются в глаза различия в поведении из пункта «5. Несовершенства UI». Кроме того, WebView может спятисотить и не загрузиться. Такой случай обязательно нужно обрабатывать на нативной стороне, чтобы пользователь не столкнулся с пустым блоком. Техническая возможность встроить WebView в нативный экран есть, это да. Однако на практике, если WebView будет блоком внутри нативного экрана, то он не сможет подстраивать свои размеры без дёрганья интерфейса. Также в этом случае его надо каким-то образом расположить среди остальных элементов, для этого необходимо понять, сколько места надо WebView при заданных ограничениях на ширину и/или высоту, ведь любой блок на экране должен занимать столько места, сколько ему нужно, не больше и не меньше. В принципе «достать» размеры, необходимые для отображения контента WebView, возможно через JS-функцию. Однако здесь сказывается факт, что отрисовка WebView работает медленнее, чем нативная, и придётся менять её размеры по колбэку, при этом, визуально экран будет дергаться. Другой вариант не лучше — ждать вычисление размеров всех блоков, что задержит отображение всей страницы, её TTI. В iOS есть дополнительная проблема. Наше приложение Циан поддерживает iPad и перевороты устройства, при которых экран не пересоздаётся (в отличии от Android), а динамически меняет свою вёрстку. Помимо разворота, наше приложение также поддерживает режим многозадачность в iPad, при котором пользователь может разделить экран на две части, чтобы одновременно пользоваться двумя приложениями. Исходя из всего этого, WebView стоит использовать только как Full-Screen экран, а не как блок. Это накладывает сильные ограничения в области применения WebView в нашем проекте. Самые ключевые экраны у нас являются нативными, и менять это не планируется. Нельзя просто так взять и переиспользовать страницу мобильного сайта :) Требуется адаптировать верстку, UX и UI, а порой и навигацию под мобильное приложение. На сайте у нас два типа верстки: мобильная и планшетная, выбор зависит от ширины экрана. Дополнительная трудность здесь заключается в том, что размер экрана, выделенного для приложения в iPad, величина непостоянная, она меняется динамически при разворотах устройства и при переходе в режим многозадачности (когда экран разделяется на 2 части). Одно вращение может превратить ширину из планшетной в мобильную, и экран должен в этот момент перестроиться динамически, ведь в iOS при смене ориентации экраны не пересоздаются. На сайте у нас это место является проблемным. Кроме того, необходимо убрать все ненужные переходы на другие фичи и навигацию по сайту, чтобы сделать пользователя мобильного приложения пользователем сайта внутри мобильного приложения. Существуют дополнительные ограничения и нюансы в работе на разных платформах. Из известных, например, в WebView на Android не работают некоторые html5 теги и не работает DatePicker. Поэтому не стоит думать, что использование WebView даёт тройной прирост в скорости. В отличии от натива, в механизме WebView нет возможности поднять в фоне сервис для получения или синхронизации данных. У нас в приложении, например, с разрешения пользователя, происходит отслеживание изменений его местоположения, даже когда приложение не запущено, данные сохраняются локально и по мере накопления отправляются на наш сервер. Конечно, это сделано нативными средствами. При использовании WebView можно столкнуться с невозможностью использования сенсоров и датчиков мобильного устройства, доступных в нативе, таких как fingerprint, nfc, акселерометр, гироскоп и т. п. В Android также невозможно или очень затруднительно из WebView взаимодействовать с другими нативными приложениями (в iOS это и в нативе практически невозможно). Кроме того, такое преимущество WebView как 2. Синхронный Update на пользователей, приводит к дополнительным рискам, когда WebView требуется обмен данными с нативной частью приложения. В этом случае может произойти поломка самой интеграции в нативное приложение, когда что-то ломается в самой логике обмена данными. В наших чисто нативных фичах такой риск значительно ниже, так как их обновления привязаны к релизному циклу, нет проблем версионирования, и потенциальная поломка должна быть предотвращена прогоном тестов еще до релиза. В случае же с WebView логика может измениться в любой момент и существует несколько вариантов поломки: Последняя версия WebView — последняя версия приложения. Последняя версия WebView — одна/несколько предыдущих версий приложения (с последней версией приложения всё в порядке). Комбо предыдущих двух пунктов. Поэтому с соблюдением контракта требуется повышенное внимание, цена ошибки может оказаться высока, особенно опасен пункт 2, ведь его гораздо сложнее, прежде всего, поймать, а также воспроизвести и починить. UI-тесты у нас интеграционные, и проверяют работу всего пользовательского флоу, а не отдельно взятого экрана. Подробнее про UI-тесты в Циан мы писали в отдельной статье. Даже когда WebView встроен отдельным экраном, могут возникать трудности с покрытием функционала UI-тестами, особенно в следующих случаях: если WebView требуется обмен данными с нативной частью; если экран WebView является не тупиковым в своем флоу. При отсутствии этих условий проблем с интеграционными UI-тестами в мобильных приложениях меньше: достаточно лишь проверить факт открытия экрана, а остальные проверки производить тестами на стороне Web. Если же хотя бы одно из этих условий применимо, для такой фичи уже недостаточно прогона UI-тестов перед обновлением приложения, так как WebView не привязан к релизному циклу (ещё один привет плюсу 2) и поломка может произойти в любой момент. Значит, если функционал фичи на WebView важен (особенно если экран WebView не тупиковый), требуется прогонять её интеграционные тесты как можно чаще. При этом дополнительные трудности возникают с их стабильностью, ведь любая проблема с интернет-соединением может привести к падению теста. В случае нативных фичей мы сводим end-to-end тесты к минимуму, заменяя реальные ответы на запросы заглушками. С WebView же этот номер так просто не пройдёт, ведь он не способен к автономной работе без сети. Соответственно, если WebView не является тупиковым во флоу пользователя, то для такой фичи требуется решение, как грамотно пройти этот шаг флоу (или сымитировать это) в прогоне UI-теста соседних экранов, чтобы одновременно и повысить стабильность теста, и не сделать его бессмысленным. Требуется чётко проработать, что и как должно тестироваться на стороне Web, что на стороне натива, как протестировать интеграцию. Если охарактеризовать одним предложением рассмотренные выше тезисы, то можно с уверенностью сказать, что инструмент WebView довольно ограничен и НЕ может дать качества нативной разработки. Мы детально рассмотрели плюсы, минусы и ограничения WebView, и теперь, на основе технических ограничений, нюансов работы и опыта использования WebView в Циан, можно перейти рекомендациям, которые мы составили для своих продуктовых команд. Вот мы и подошли к нашему ответу на вопрос: «В каких случаях использование WebView уместно, а когда лучше разрабатывать нативно?». У вас MVP фича или эксперимент, в рамках которого хочется проверить гипотезу, не затратив при этом много ресурсов. Здесь всё понятно, в данном случае большая часть вытекающих от использования WebView недоработок не являются критичными. На старте проекта следует лишь внимательно посмотреть, позволяют ли ограничения инструмента реализовать проект даже на уровне MVP. При завершении эксперимента на WebView следует осознанно принять решение о дальнейшем способе реализации, желательно, в сторону натива. В некоторых фичах требуется использовать сторонние технологии или сервисы, реализованные только для браузера и не имеющие нативного SDK. В этом случае стоит провести ресёрч, есть ли подходящие аналоги. Если аналогов нет, то и выбора нет — придётся делать на WebView. В этом случае нужно уделить особое внимание флоу при проработке фичи, чтобы максимально аккуратно интегрировать такую фичу в приложение. Что вкладывается в слово «обособлена»? Фича имеет и в будущем будет продолжать иметь отдельное логическое ответвление функционала, который линеен, конечен и никак не пересекается с нативной реализацией других фичей в приложении: фича на отдельном экране, а не в виде блока на другом экране; флоу фичи тупиковый, а не сквозной, то есть на эту фичу можно попасть, а дальнейший путь пользователя лежит обратно к месту входа; не требуется активный обмен данными с остальной частью приложения. Когда у фичи простое назначение, от пользователя не требуется совершить в рамках флоу много действий, то пользователи будут испытывать меньше неудобств от WebView. Такая фича представляет из себя простой лендинг или функционал, связанный только с отображением информации, не обладает большим количеством интерактива и переходов. Это может быть какое-либо соглашение или промостраница. Если эксперименты прошли успешно, вы в фиче не сомневаетесь, уверены в том, что она в приложении на долгий срок и будет только продолжать развиваться. Здесь всё просто, мы рассмотрели ограничения и минусы, если фиче требуется возможности, которых нет у WebView, то и WebView для неё не подходит. Обратите внимание на пункты 2, 9, 10 в списке минусов. WebView, к сожалению, не может дать качества натива и рассмотренные минусы и ограничения будут чувствоваться острее в фичах, на функционал которых мы целенаправленно ведём пользователя. Чем чаще пользователям предстоит иметь дело с экраном, тем меньше неудобств это должно им доставлять. Минусы мы постарались подробно рассмотреть выше. Когда фича тесно не в одностороннем порядке связана с другим функционалом приложения, к WebView лучше не прибегать. Вот небольшой чек-лист для фичи, которая может называться обособленной: отдельный экран, а не блок внутри другого; экран фичи является последним/тупиковым во флоу пользователя (при этом не целевой); обмен данными с основным приложением сведён к минимуму. Обратите внимание на пункты 6, 7, 11, 12 в списке минусов. Необособленную фичу у нас в Циан обязательно нужно согласовать с командами, с функционалом которых они тесно связаны. Хотя связанные фичи у нас принято согласовывать независимо от её технологий :) Чем больше действий требуется произвести пользователям в рамках функционала фичи, тем меньше подходит для этого WebView. Чем больше интерактива на экране, тем сложнее для него будет загримировать WebView под натив. С чем предстоит столкнуться, мы постарались подробно рассмотреть в минусах выше. Скажем, заполнять форму своими данными удобнее в интерфейсе с максимально привычными контролами (форма здесь просто самый очевидный пример, но не единственный). Итак, давайте рассмотрим, как работают наши рекомендации на популярном примере экрана «Лицензионные соглашения». Почему его реализация на WebView оправдана: Лицензионное соглашение может измениться в любой момент, при этом требования чаще всего таковы, что оно должно быть одинаковым для всех версий приложения; Экран тупиковый, пользователь может только вернуться с него обратно на тот экран, откуда пришёл; Нет никакого обмена данными и общей логики с другими экранами приложения; Носит чисто информационный характер с минимумом интерактива. Для данного экрана можно даже не пытаться загримировать WebView под натив. Мы так и поступили :) Мы понимаем, что каждый проект по-своему уникален, и все возможные условия не охватить в рамках одной статьи. Мы просто постарались рассказать о возможных проблемах и собрать рекомендации, которые, надеемся, помогут критически взглянуть на вопрос использования WebView с разных сторон и принять взвешенное решение. Время, которое будет потрачено на синхронизацию и проработку этого вопроса на старте проекта, несравнимо меньше времени, которое предстоит потратить на переделки в случае ошибки. У нас в Циан эти рекомендации не являются строгим запретом, однако все команды стараются их придерживаться. Если команда понимает, что WebView – их вариант, то: Она старается проработать флоу таким образом, чтобы фича была обособлена настолько, насколько это возможно; Синхронизируется о решении с командами, функционал которых потенциально может быть затронут. Это у нас и так принято делать независимо от технологий. Так что живем дружно :)  iOS-разработчик