Архитектура современных приложений часто позволяет передавать логику между клиентом и бэкендом. Главное — не зайти в этих экспериментах слишком далеко. Разработчик iOS-приложения Авто.ру Сергей Сергеев объяснил, как его команда пришла к backend driven UI не самым легким способом.
— Всем привет. Я iOS-разработчик, но история будет кроссплатформенная.
Всё началось с задачи. Приходит менеджер и говорит: хотим запустить отчеты про автомобили, это будет какая-то информация на одном экране. Информация разная. Мы, Авто.ру, какую-то информацию можем собрать об автомобиле, запросить что-то из баз ГИБДД. Есть еще автосервисы, на старте у нас был Fitservice.
Немаловажная часть — нужно эту информацию показывать как можно быстрее. Пользователь уже купил отчет о машине, а мы можем дополнить информацию для него.
![](https://habrastorage.org/r/w780q1/webt/ng/md/oi/ngmdoi_9h_hgoaethnb8nbvqxvo.jpeg)
Начинаем просто. На мобилке красим JSON, бэкенд его нам подготавливает.
![](https://habrastorage.org/r/w780q1/webt/rm/dw/ol/rmdwolb_flskmekbqct1wggibwa.jpeg)
Разные типы блоков с разными типами данных. Данные с Авто.ру у нас идут стрингами, мы пользователю сообщаем, что произошло. Данные из баз ГИБДД более сложные, с распечаткой, куда машина ударилась, как произошло ДТП и прочее.
![](https://habrastorage.org/r/w780q1/webt/i8/hz/jf/i8hzjfaw8dvnicrzsrtn4z25nfo.jpeg)
Получается так. Есть key-value, можем подсветить информацию для пользователя. Дибо сделать более сложные блоки, как с ДТП, с автосервисами.
![](https://habrastorage.org/r/w780q1/webt/-b/p9/jg/-bp9jgdnnnqtll1n-nkxg7lpxck.jpeg)
Обычная разработка мобильного приложения, пока ничего сложного.
![](https://habrastorage.org/r/w780q1/webt/9k/p9/ej/9kp9ejek46ollw7qrppnrgzmtx8.jpeg)
Кроме как на бэкенде. Им пришлось сделать отдельную ручку. Есть ручка, которая просто дает данные для отчета, а есть ручка, которая подготавливает нам эти блоки. Ничего, они могут с этим работать.
![](https://habrastorage.org/r/w780q1/webt/wp/sg/us/wpsguscwbjxuhp9_0r1wevlmrye.jpeg)
В итоге мы можем менять блоки местами и добавлять блоки одного и того же типа. Можем хоть 20 автосервисов подключить. Но на кофепоинте ходят слухи, что автосервисы смогут нам отдавать распечатки по авто — какие детали они меняли или какие работы производили. Конечно, нам это тоже хочется отдавать, и тоже мгновенно. Но, понятное дело, наша модель здесь ничего сделать не сможет.
![](https://habrastorage.org/r/w780q1/webt/lx/yl/1e/lxyl1epjjfzzl4bjxuwkc5syo6u.jpeg)
Мы думаем, как сделать красиво, а бэкенд-команда просто наблюдает, что мы делаем.
![](https://habrastorage.org/r/w780q1/webt/98/gf/u_/98gfu_kcgbdyd5tfaenbhlp6sam.jpeg)
Есть обычное решение — сделать WebView. Но выглядит не очень красиво. Не то чтобы менеджеры были против, скорее со стороны разработки не хочется.
![](https://habrastorage.org/r/w780q1/webt/mx/dl/4g/mxdl4goguzptx3tqopxtmwg2mie.jpeg)
Мы не успели посмотреть какие-то кроссплатформенные фреймворки — потому что у нас уже было решение. Все давним-давно сделали у себя в приложениях сторис, далеко не только Instagram. Даже если вы продаете квартиры — у вас обязательно должны быть сторис.
![](https://habrastorage.org/r/w780q1/webt/nz/wp/ep/nzwpepnnwyhu6ms0rej4lnijywe.jpeg)
В сторис можно показывать любой контент, он одинаковый для обеих платформ, потому что это новости. Верстка, конечно, должна быть серверной, потому что все сторис обновляются. У нас это сделано на интересном движке от Facebook — Yoga. Он является core-частью фреймворка ComponentKit, если под iOS, и Litho, если под Android. Также это core-часть React Native.
Как мы к нему пришли? Коротко скажу, что на iOS мы пишем не всё. Если вам интересно, как пришли, вот крутой доклад:
Если кто устал от constraints — обязательно гляньте. Там и про Android, и про iOS. В iOS мы пришли к этому во всем приложении, в Android затащили для сторис.
![](https://habrastorage.org/r/w780q1/webt/xh/9y/lz/xh9ylzltrduumnb0xuhsip_d__k.jpeg)
Рассмотрим, как это работает внутри. Код читать необязательно, тем более что тут XML, ничего интересного. XML нам дает порядок и вложенность UI-элементов, а редакторам позволяет делать их задачи удобно. В XML сразу видно, где и какие элементы есть.
![](https://habrastorage.org/r/w780q1/webt/mg/d6/kw/mgd6kwmntehzqkoqtzi-xrcq7w8.jpeg)
Дальше нам надо понять, как эти элементы будут располагаться в своей вложенности. Здесь нам на помощь как раз приходит фреймворк Yoga. Это реализация Flexbox для мобильных девайсов. Тут тоже не буду долго останавливаться, если кто-то уже работал с Flexbox или знает, что это, — понимает, в чем тут магия: ты буквально за 20 параметров можешь расположить элемент в любой точке экрана. Для тех, кто не знает, оставлю ссылку на сайт Yoga layout, там есть отличное демо. Можно потыкать и особо ничего не читать.
![](https://habrastorage.org/r/w780q1/webt/bb/jx/zi/bbjxzi8jqr4k-hmdwcu8rhv193e.jpeg)
Когда мы научились располагать элементы в каком-то порядке и в любом положении, нам осталось только затянуть отображение. Но в отображение мы передаем те элементы, которые нужно отобразить в приложении, здесь все ограничивается вашей любимой библиотекой. У нас это UIKit.
XML дает нам свободу, любую вложенность, любое расположение. На отображении — параметры от UIKit. Есть хитрое наследование тегов. Это тег stack, который как раз использует вложенность, и по сути это view. В iOS — UIView, в Android — своя корневая View, у нее есть набор компонентов, позволяющих что-то разукрасить, добавить закругления. Все новые view от нее наследуются.
Предположим, нам нужен текст. Заводим тег text, он наследует все параметры от stack плюс, возможно, добавляет свои по тексту и по цвету — то, что вам нужно.
![](https://habrastorage.org/r/w780q1/webt/fc/5k/on/fc5konbzlfjoo0z5byq5zh0muwi.jpeg)
Так мы делаем действительно гибкий UI. Можем затащить техосмотры, это новая информация, которой у пользователей сразу после релиза не было, а мы можем с сервера прислать этот новый блок и информацию из него. Можем компоновать информацию. Например, мы посмотрели, какой владелец в какое примерно время у этой машины был. В то же время у нее был техосмотр. Можем скомпоновать все это в отдельном блоке и показать на девайсе.
Либо можем подсветить важную информацию, например, про налоги. Здесь все хорошо, статика.
![](https://habrastorage.org/r/w780q1/webt/ol/mv/9m/olmv9mz7chk9punkurudm0vtu3q.jpeg)
Но что с взаимодействием? Мы же отображаем старые объявления с Авто.ру. Я уже раскрыл подробности: у нас все приложение покрыто диплинками, и здесь они спасают нас примерно в 100% случаев. Например, нам нужно открыть экран объявления. Прокидываем туда ссылку, по ней открывается экран, пытаемся запарсить его, как диплинк. Если не умеем — открываем в WebView, умеем — открываем новый экран. Пользователь чувствует, что это натив, и это он и есть.
![](https://habrastorage.org/r/w780q1/webt/od/r-/is/odr-isfkvsbckbe2uofobybzlpo.jpeg)
Дальше мы не только кликаем. Есть еще и контент, пользователю нужно обеспечить взаимодействие с этим контентом. У нас есть много фотографий машины, нужно поместить их в карусель. Здесь мы делаем workaround в том же XML — заводим под карусель тег scrollableLayout. Все child этого тега наследуют всё от того же View и становятся в CollectionView, то есть в карусель.
![](https://habrastorage.org/r/w780q1/webt/mi/2p/lu/mi2plu7k20ml7akiejiwzlcivpg.jpeg)
Также мы затащили «расхлопы». В XML сразу приходит верстка для схлопнутого и расхлопнутого блока, а приложение переключает эти верстки.
![](https://habrastorage.org/r/w780q1/webt/si/yx/fz/siyxfzighk6qfh-qbe5ir78-dgy.jpeg)
Примерно то же самое и с pop-up. Чтобы не выливать на пользователя много информации сразу, прячем ее в pop-up. Верстка pop-up приходит в XML, по кнопке приложение понимает, когда эту верстку отобразить.
![](https://habrastorage.org/r/w780q1/webt/t_/q4/ok/t_q4okf8kp_li60pierpqj4f6jm.jpeg)
Что мы совсем не можем этой крутой версткой сделать — красивые графики. Менеджеры, конечно, хотят, но такого не сделаешь. Он кликабельный, можно посмотреть историю пробегов у машины, но в XML не заверстаешь. Приходится на месте, где должен быть график, ставить тег: здесь расположен график. Приложение поймет, какую view использовать. То есть уже не содержится информации о том, какая view нужна.
Есть минусы: XML, который я показывал в старой версии, поддерживается, пользователю не обязательно обновляться, а вот поддерживать красивые кастомные view будут только новые версии приложений. Но это пограничный кейс, графиков у нас не так много.
Катить XML действительно круто, все отображение приходит с сервера. Пример с блоком расчета стоимости — это реальная задача, она пришла с готовым бэкендом, с дизайнами, от постановки задачи до появления блока на экране пользователя действительно прошла неделя. Три дня заняла разработка — верстка на XML, тестирование и выкладка XML, который приходит на девайсы.
Работает как магия — ровно до того момента, пока мы не начинаем задаваться вопросом, откуда брать весь этот XML. Он же динамический. Нужно, чтобы его кто-то генерировал. Команда бэкенда на нас смотрит и говорит — это что же, нам его нужно присылать?
![](https://habrastorage.org/r/w780q1/webt/vf/mu/41/vfmu41eispx06uowr7ykbvdtmh4.jpeg)
Подождите, мы можем сделать это, как делают фронтендеры. А они делают статичные сайты на HTML, используют шаблонизаторы. Можно сделать шаблон, плейсхолдеры, забить его JSON, и на выходе получится нормальная верстка. Но нет, мы исследовали и нам это не подходит: в верстке у нас содержится бизнес-логика, то есть расположение, очередность, цвета элементов — в зависимости от того, что происходит в отчете. Предположим, пользователь указал пять владельцев, а было 10. Мы все это подсвечиваем на девайсе, то есть в верстке. Возможно, это не очень правильно, но нам с этим удобно работать. Получается, что шаблонизаторы тут совсем не подходят.
Задача должна куда-то двигаться. Мы уже показали наше крутое решение менеджерам, они уже хотят видеть это в проде, а мы так и не решили проблемы с XML. В итоге принимаем решение, что нам его будет отдавать бэкенд.
В этом как будто бы ничего плохого нет. Мобильная разработка умеет верстать под XML, несет его в бэкенд, а бэкенд делает точно так же и присылает нам в мобилку.
![](https://habrastorage.org/r/w780q1/webt/hq/n1/tt/hqn1ttcrmgzja-8sisq2qjn4i4m.jpeg)
Это реальный комментарий и задача. Понятно, что мобильная разработка есть на iOS и Android, два разработчика верстают, третий разработчик на бэкенде эту XML программирует, как написано, и несем в QA тестировать. Мем примерно отражает ту эпоху.
Наш разработчик пишет на Scala. Это не то чтобы плохой язык для верстки XML. Проблема тут даже не в языке.
![](https://habrastorage.org/r/w780q1/webt/2o/uk/g2/2oukg2bfbkuwplbvwyxu8jffa_i.jpeg)
Проблема в бэкенд-разработчике. Мы ему говорим — здесь приходит что-то неправильное.
![](https://habrastorage.org/r/w780q1/webt/b1/qy/zk/b1qyzkaotpntg0skbspvc6r9zoa.jpeg)
Конечно, он здесь ничего не видит. Какие еще отступы? Процесс получается так себе. Любые изменения в UI затрагивают и нас, потому что мы знаем, как это должно выглядеть, и бэкенд-разработку, потому что она это нам отдает, и дизайнера, который при виде нашего кривого UI идет к нам ругаться. Все это отдельно верстается под Android, потому что логика в итоге разошлась.
![](https://habrastorage.org/r/w780q1/webt/ai/t1/i7/ait1i71cc7vzyq6wixi8peyi7zy.jpeg)
Как выглядит интерфейс? XML становится только больше, потому что подключаются новые блоки и сервисы.
В итоге на мобильных мы радуемся, но сервер в лице одного старшего Scala-девелопера, к сожалению, страдает.
![](https://habrastorage.org/r/w780q1/webt/io/0e/9z/io0e9zurdfffgbhgs_vqu1kf75e.jpeg)
Перейдем к третьей части, в которой, я надеюсь, все перестанут верстать XML. Нам самим надо научиться генерировать XML, потому что мы знаем, как он выглядит. Но генератор должен быть динамический, мы не можем зашить его в приложение — как мы тогда будем его изменять?
Так как падать нам дальше некуда, мы можем генерировать XML JS. Это удобно, в нем можно описать достаточно сложную логику, а JS мы можем гонять еще и прямо на девайсе. В iOS это делается буквально в одну строчку. Ты берешь WKWebView, говоришь: .evaluateJavaScript, кидаешь туда JS, и происходит магия. В Android немного сложнее, но все же работать оно будет.
Получается, что UI занимаемся мы, JS лежит в репозитории, мы его туда пушим, редактируем, тестируем то, что получилось, и катим в прод.
![](https://habrastorage.org/r/w780q1/webt/fb/yw/v_/fbywv_4zcozkywilk9jfecgdgjg.jpeg)
Больше не задействуем серверную разработку, она больше не занимается UI, тем более что у нее осталась только одна ручка, которая отдает данные из отчета и всё. Они совсем к нам отношения не имеют, и кажется, что уже и не хотят.
Мы научились работать с JS. JavaScript-код интересно писать после строго типизированных языков. Но дальше тысячной строчки мы не смогли без типов, поэтому завезли себе TypeScript и структурировали код. Каждый блок — это класс на TypeScript. Если нужно поправить какой-то блок — ищешь его прямо в TypeScript и правишь, все достаточно легко. Процесс стал такой, как и мы задумывали в самом начале. Менеджер приходит к нам, мы правим, нам прилетают данные из бэка, и всей версткой тоже занимаемся мы. При этом всю верстку мы присылаем с сервера.
Казалось бы, все хорошо. Но начались проблемы. Решение гонять JS на мобилках было все же спонтанным. Поначалу был какой-то процент ошибок, на который мы не обращали внимания, но потом этот процент перерос в большое количество людей, отчетов стало больше, покупать их стали больше. На iOS есть особенность: WKWebView запускается в своем процессе, отдельном от процесса самого приложения. Это, с одной стороны, интересно: ресурсы, которые выделяются на аппприложение, не выедаются. А с другой — не очень интересно, потому что за этим процессом невозможно следить, он может вернуть непонятные ошибки, процент которых у нас и был. На Android все было совсем плохо: туда затащили старую библиотеку Rhino, она поддерживала не самый новый JS и долго стартовала. На Android тоже жаловались, там было на что жаловаться.
![](https://habrastorage.org/r/w780q1/webt/pf/y7/fh/pfy7fhcrbx0frmnuhu19cnr-ilg.jpeg)
Это уже часть 3.1, где все работает, но хочется чуть лучше. Напрашивается нормальное решение — перенести генерацию XML с девайсов, перестать делать это мощностями пользователей. Мы вызываем команду бэкенда на встречу.
Android-разработчик держит дверь, а я объясняю, что вы снова будете присылать нам верстку, но заниматься ей мы будем сами. Нам нужно, чтобы вы всё настроили, подняли у себя штуку, которая будет делать JS, графы и всё, что вы любите. А нам только JS прогнать.
На этом этапе мы сейчас и находимся. Хеппи-энд? Пока непонятно. Сейчас оно работает так, как мы и задумывали изначально, нам комфортно катить все изменения. На TypeScript писать тоже комфортно и даже интересно.
Из этого надо сделать выводы. Если какие-то серьезные, то, залезая в backend-driven UI, нужно либо сразу вводить дизайн-систему, либо смириться с тем, что дизайн на двух платформах разойдется. Нельзя просто сказать: «У нас вроде одинаковая библиотека, и все должно работать так же». Android-разработчики думают по-другому, как и я с их точки зрения.
Если делать шуточные выводы, то XML можно писать до 500-й строчки, дальше уже путаешься. JS без типов можно писать где-то до тысячной, дальше теряешь контекст и становится сложно. И еще бэкенд-разработчики точно не умеют в верстку, не стоит давать им верстать, по крайней мере Scala-разработчикам. Это всё, спасибо.
— Всем привет. Я iOS-разработчик, но история будет кроссплатформенная.
Всё началось с задачи. Приходит менеджер и говорит: хотим запустить отчеты про автомобили, это будет какая-то информация на одном экране. Информация разная. Мы, Авто.ру, какую-то информацию можем собрать об автомобиле, запросить что-то из баз ГИБДД. Есть еще автосервисы, на старте у нас был Fitservice.
Немаловажная часть — нужно эту информацию показывать как можно быстрее. Пользователь уже купил отчет о машине, а мы можем дополнить информацию для него.
![](https://habrastorage.org/webt/ng/md/oi/ngmdoi_9h_hgoaethnb8nbvqxvo.jpeg)
Начинаем просто. На мобилке красим JSON, бэкенд его нам подготавливает.
![](https://habrastorage.org/webt/rm/dw/ol/rmdwolb_flskmekbqct1wggibwa.jpeg)
Разные типы блоков с разными типами данных. Данные с Авто.ру у нас идут стрингами, мы пользователю сообщаем, что произошло. Данные из баз ГИБДД более сложные, с распечаткой, куда машина ударилась, как произошло ДТП и прочее.
![](https://habrastorage.org/webt/i8/hz/jf/i8hzjfaw8dvnicrzsrtn4z25nfo.jpeg)
Получается так. Есть key-value, можем подсветить информацию для пользователя. Дибо сделать более сложные блоки, как с ДТП, с автосервисами.
![](https://habrastorage.org/webt/-b/p9/jg/-bp9jgdnnnqtll1n-nkxg7lpxck.jpeg)
Обычная разработка мобильного приложения, пока ничего сложного.
![](https://habrastorage.org/webt/9k/p9/ej/9kp9ejek46ollw7qrppnrgzmtx8.jpeg)
Кроме как на бэкенде. Им пришлось сделать отдельную ручку. Есть ручка, которая просто дает данные для отчета, а есть ручка, которая подготавливает нам эти блоки. Ничего, они могут с этим работать.
![](https://habrastorage.org/webt/wp/sg/us/wpsguscwbjxuhp9_0r1wevlmrye.jpeg)
В итоге мы можем менять блоки местами и добавлять блоки одного и того же типа. Можем хоть 20 автосервисов подключить. Но на кофепоинте ходят слухи, что автосервисы смогут нам отдавать распечатки по авто — какие детали они меняли или какие работы производили. Конечно, нам это тоже хочется отдавать, и тоже мгновенно. Но, понятное дело, наша модель здесь ничего сделать не сможет.
![](https://habrastorage.org/webt/lx/yl/1e/lxyl1epjjfzzl4bjxuwkc5syo6u.jpeg)
Мы думаем, как сделать красиво, а бэкенд-команда просто наблюдает, что мы делаем.
![](https://habrastorage.org/webt/98/gf/u_/98gfu_kcgbdyd5tfaenbhlp6sam.jpeg)
Есть обычное решение — сделать WebView. Но выглядит не очень красиво. Не то чтобы менеджеры были против, скорее со стороны разработки не хочется.
![](https://habrastorage.org/webt/mx/dl/4g/mxdl4goguzptx3tqopxtmwg2mie.jpeg)
Мы не успели посмотреть какие-то кроссплатформенные фреймворки — потому что у нас уже было решение. Все давним-давно сделали у себя в приложениях сторис, далеко не только Instagram. Даже если вы продаете квартиры — у вас обязательно должны быть сторис.
![](https://habrastorage.org/webt/nz/wp/ep/nzwpepnnwyhu6ms0rej4lnijywe.jpeg)
В сторис можно показывать любой контент, он одинаковый для обеих платформ, потому что это новости. Верстка, конечно, должна быть серверной, потому что все сторис обновляются. У нас это сделано на интересном движке от Facebook — Yoga. Он является core-частью фреймворка ComponentKit, если под iOS, и Litho, если под Android. Также это core-часть React Native.
Как мы к нему пришли? Коротко скажу, что на iOS мы пишем не всё. Если вам интересно, как пришли, вот крутой доклад:
Скрытый текст
Если кто устал от constraints — обязательно гляньте. Там и про Android, и про iOS. В iOS мы пришли к этому во всем приложении, в Android затащили для сторис.
![](https://habrastorage.org/webt/xh/9y/lz/xh9ylzltrduumnb0xuhsip_d__k.jpeg)
Рассмотрим, как это работает внутри. Код читать необязательно, тем более что тут XML, ничего интересного. XML нам дает порядок и вложенность UI-элементов, а редакторам позволяет делать их задачи удобно. В XML сразу видно, где и какие элементы есть.
![](https://habrastorage.org/webt/mg/d6/kw/mgd6kwmntehzqkoqtzi-xrcq7w8.jpeg)
Дальше нам надо понять, как эти элементы будут располагаться в своей вложенности. Здесь нам на помощь как раз приходит фреймворк Yoga. Это реализация Flexbox для мобильных девайсов. Тут тоже не буду долго останавливаться, если кто-то уже работал с Flexbox или знает, что это, — понимает, в чем тут магия: ты буквально за 20 параметров можешь расположить элемент в любой точке экрана. Для тех, кто не знает, оставлю ссылку на сайт Yoga layout, там есть отличное демо. Можно потыкать и особо ничего не читать.
![](https://habrastorage.org/webt/bb/jx/zi/bbjxzi8jqr4k-hmdwcu8rhv193e.jpeg)
Когда мы научились располагать элементы в каком-то порядке и в любом положении, нам осталось только затянуть отображение. Но в отображение мы передаем те элементы, которые нужно отобразить в приложении, здесь все ограничивается вашей любимой библиотекой. У нас это UIKit.
XML дает нам свободу, любую вложенность, любое расположение. На отображении — параметры от UIKit. Есть хитрое наследование тегов. Это тег stack, который как раз использует вложенность, и по сути это view. В iOS — UIView, в Android — своя корневая View, у нее есть набор компонентов, позволяющих что-то разукрасить, добавить закругления. Все новые view от нее наследуются.
Предположим, нам нужен текст. Заводим тег text, он наследует все параметры от stack плюс, возможно, добавляет свои по тексту и по цвету — то, что вам нужно.
![](https://habrastorage.org/webt/fc/5k/on/fc5konbzlfjoo0z5byq5zh0muwi.jpeg)
Так мы делаем действительно гибкий UI. Можем затащить техосмотры, это новая информация, которой у пользователей сразу после релиза не было, а мы можем с сервера прислать этот новый блок и информацию из него. Можем компоновать информацию. Например, мы посмотрели, какой владелец в какое примерно время у этой машины был. В то же время у нее был техосмотр. Можем скомпоновать все это в отдельном блоке и показать на девайсе.
Либо можем подсветить важную информацию, например, про налоги. Здесь все хорошо, статика.
![](https://habrastorage.org/webt/ol/mv/9m/olmv9mz7chk9punkurudm0vtu3q.jpeg)
Но что с взаимодействием? Мы же отображаем старые объявления с Авто.ру. Я уже раскрыл подробности: у нас все приложение покрыто диплинками, и здесь они спасают нас примерно в 100% случаев. Например, нам нужно открыть экран объявления. Прокидываем туда ссылку, по ней открывается экран, пытаемся запарсить его, как диплинк. Если не умеем — открываем в WebView, умеем — открываем новый экран. Пользователь чувствует, что это натив, и это он и есть.
![](https://habrastorage.org/webt/od/r-/is/odr-isfkvsbckbe2uofobybzlpo.jpeg)
Дальше мы не только кликаем. Есть еще и контент, пользователю нужно обеспечить взаимодействие с этим контентом. У нас есть много фотографий машины, нужно поместить их в карусель. Здесь мы делаем workaround в том же XML — заводим под карусель тег scrollableLayout. Все child этого тега наследуют всё от того же View и становятся в CollectionView, то есть в карусель.
![](https://habrastorage.org/webt/mi/2p/lu/mi2plu7k20ml7akiejiwzlcivpg.jpeg)
Также мы затащили «расхлопы». В XML сразу приходит верстка для схлопнутого и расхлопнутого блока, а приложение переключает эти верстки.
![](https://habrastorage.org/webt/si/yx/fz/siyxfzighk6qfh-qbe5ir78-dgy.jpeg)
Примерно то же самое и с pop-up. Чтобы не выливать на пользователя много информации сразу, прячем ее в pop-up. Верстка pop-up приходит в XML, по кнопке приложение понимает, когда эту верстку отобразить.
![](https://habrastorage.org/webt/t_/q4/ok/t_q4okf8kp_li60pierpqj4f6jm.jpeg)
Что мы совсем не можем этой крутой версткой сделать — красивые графики. Менеджеры, конечно, хотят, но такого не сделаешь. Он кликабельный, можно посмотреть историю пробегов у машины, но в XML не заверстаешь. Приходится на месте, где должен быть график, ставить тег: здесь расположен график. Приложение поймет, какую view использовать. То есть уже не содержится информации о том, какая view нужна.
Есть минусы: XML, который я показывал в старой версии, поддерживается, пользователю не обязательно обновляться, а вот поддерживать красивые кастомные view будут только новые версии приложений. Но это пограничный кейс, графиков у нас не так много.
Катить XML действительно круто, все отображение приходит с сервера. Пример с блоком расчета стоимости — это реальная задача, она пришла с готовым бэкендом, с дизайнами, от постановки задачи до появления блока на экране пользователя действительно прошла неделя. Три дня заняла разработка — верстка на XML, тестирование и выкладка XML, который приходит на девайсы.
Работает как магия — ровно до того момента, пока мы не начинаем задаваться вопросом, откуда брать весь этот XML. Он же динамический. Нужно, чтобы его кто-то генерировал. Команда бэкенда на нас смотрит и говорит — это что же, нам его нужно присылать?
![](https://habrastorage.org/webt/vf/mu/41/vfmu41eispx06uowr7ykbvdtmh4.jpeg)
Подождите, мы можем сделать это, как делают фронтендеры. А они делают статичные сайты на HTML, используют шаблонизаторы. Можно сделать шаблон, плейсхолдеры, забить его JSON, и на выходе получится нормальная верстка. Но нет, мы исследовали и нам это не подходит: в верстке у нас содержится бизнес-логика, то есть расположение, очередность, цвета элементов — в зависимости от того, что происходит в отчете. Предположим, пользователь указал пять владельцев, а было 10. Мы все это подсвечиваем на девайсе, то есть в верстке. Возможно, это не очень правильно, но нам с этим удобно работать. Получается, что шаблонизаторы тут совсем не подходят.
Задача должна куда-то двигаться. Мы уже показали наше крутое решение менеджерам, они уже хотят видеть это в проде, а мы так и не решили проблемы с XML. В итоге принимаем решение, что нам его будет отдавать бэкенд.
В этом как будто бы ничего плохого нет. Мобильная разработка умеет верстать под XML, несет его в бэкенд, а бэкенд делает точно так же и присылает нам в мобилку.
![](https://habrastorage.org/webt/hq/n1/tt/hqn1ttcrmgzja-8sisq2qjn4i4m.jpeg)
Это реальный комментарий и задача. Понятно, что мобильная разработка есть на iOS и Android, два разработчика верстают, третий разработчик на бэкенде эту XML программирует, как написано, и несем в QA тестировать. Мем примерно отражает ту эпоху.
Наш разработчик пишет на Scala. Это не то чтобы плохой язык для верстки XML. Проблема тут даже не в языке.
![](https://habrastorage.org/webt/2o/uk/g2/2oukg2bfbkuwplbvwyxu8jffa_i.jpeg)
Проблема в бэкенд-разработчике. Мы ему говорим — здесь приходит что-то неправильное.
![](https://habrastorage.org/webt/b1/qy/zk/b1qyzkaotpntg0skbspvc6r9zoa.jpeg)
Конечно, он здесь ничего не видит. Какие еще отступы? Процесс получается так себе. Любые изменения в UI затрагивают и нас, потому что мы знаем, как это должно выглядеть, и бэкенд-разработку, потому что она это нам отдает, и дизайнера, который при виде нашего кривого UI идет к нам ругаться. Все это отдельно верстается под Android, потому что логика в итоге разошлась.
![](https://habrastorage.org/webt/ai/t1/i7/ait1i71cc7vzyq6wixi8peyi7zy.jpeg)
Как выглядит интерфейс? XML становится только больше, потому что подключаются новые блоки и сервисы.
В итоге на мобильных мы радуемся, но сервер в лице одного старшего Scala-девелопера, к сожалению, страдает.
![](https://habrastorage.org/webt/io/0e/9z/io0e9zurdfffgbhgs_vqu1kf75e.jpeg)
Перейдем к третьей части, в которой, я надеюсь, все перестанут верстать XML. Нам самим надо научиться генерировать XML, потому что мы знаем, как он выглядит. Но генератор должен быть динамический, мы не можем зашить его в приложение — как мы тогда будем его изменять?
Так как падать нам дальше некуда, мы можем генерировать XML JS. Это удобно, в нем можно описать достаточно сложную логику, а JS мы можем гонять еще и прямо на девайсе. В iOS это делается буквально в одну строчку. Ты берешь WKWebView, говоришь: .evaluateJavaScript, кидаешь туда JS, и происходит магия. В Android немного сложнее, но все же работать оно будет.
Получается, что UI занимаемся мы, JS лежит в репозитории, мы его туда пушим, редактируем, тестируем то, что получилось, и катим в прод.
![](https://habrastorage.org/webt/fb/yw/v_/fbywv_4zcozkywilk9jfecgdgjg.jpeg)
Больше не задействуем серверную разработку, она больше не занимается UI, тем более что у нее осталась только одна ручка, которая отдает данные из отчета и всё. Они совсем к нам отношения не имеют, и кажется, что уже и не хотят.
Мы научились работать с JS. JavaScript-код интересно писать после строго типизированных языков. Но дальше тысячной строчки мы не смогли без типов, поэтому завезли себе TypeScript и структурировали код. Каждый блок — это класс на TypeScript. Если нужно поправить какой-то блок — ищешь его прямо в TypeScript и правишь, все достаточно легко. Процесс стал такой, как и мы задумывали в самом начале. Менеджер приходит к нам, мы правим, нам прилетают данные из бэка, и всей версткой тоже занимаемся мы. При этом всю верстку мы присылаем с сервера.
Казалось бы, все хорошо. Но начались проблемы. Решение гонять JS на мобилках было все же спонтанным. Поначалу был какой-то процент ошибок, на который мы не обращали внимания, но потом этот процент перерос в большое количество людей, отчетов стало больше, покупать их стали больше. На iOS есть особенность: WKWebView запускается в своем процессе, отдельном от процесса самого приложения. Это, с одной стороны, интересно: ресурсы, которые выделяются на аппприложение, не выедаются. А с другой — не очень интересно, потому что за этим процессом невозможно следить, он может вернуть непонятные ошибки, процент которых у нас и был. На Android все было совсем плохо: туда затащили старую библиотеку Rhino, она поддерживала не самый новый JS и долго стартовала. На Android тоже жаловались, там было на что жаловаться.
![](https://habrastorage.org/webt/pf/y7/fh/pfy7fhcrbx0frmnuhu19cnr-ilg.jpeg)
Это уже часть 3.1, где все работает, но хочется чуть лучше. Напрашивается нормальное решение — перенести генерацию XML с девайсов, перестать делать это мощностями пользователей. Мы вызываем команду бэкенда на встречу.
Android-разработчик держит дверь, а я объясняю, что вы снова будете присылать нам верстку, но заниматься ей мы будем сами. Нам нужно, чтобы вы всё настроили, подняли у себя штуку, которая будет делать JS, графы и всё, что вы любите. А нам только JS прогнать.
На этом этапе мы сейчас и находимся. Хеппи-энд? Пока непонятно. Сейчас оно работает так, как мы и задумывали изначально, нам комфортно катить все изменения. На TypeScript писать тоже комфортно и даже интересно.
Из этого надо сделать выводы. Если какие-то серьезные, то, залезая в backend-driven UI, нужно либо сразу вводить дизайн-систему, либо смириться с тем, что дизайн на двух платформах разойдется. Нельзя просто сказать: «У нас вроде одинаковая библиотека, и все должно работать так же». Android-разработчики думают по-другому, как и я с их точки зрения.
Если делать шуточные выводы, то XML можно писать до 500-й строчки, дальше уже путаешься. JS без типов можно писать где-то до тысячной, дальше теряешь контекст и становится сложно. И еще бэкенд-разработчики точно не умеют в верстку, не стоит давать им верстать, по крайней мере Scala-разработчикам. Это всё, спасибо.